diff --git a/app/build.gradle b/app/build.gradle index 585099975..1b0c7d4b0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,6 +54,7 @@ android { if (project.hasProperty("RELEASE_STORE_FILE")) { signingConfig signingConfigs.myConfig } + applicationIdSuffix '.release' minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -123,7 +124,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" //room - def room_version = '2.2.3' + def room_version = '2.2.4' implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" @@ -139,7 +140,7 @@ dependencies { implementation 'com.jeremyliao:live-event-bus-x:1.4.5' //协程 - def coroutines_version = '1.2.2' + def coroutines_version = '1.3.3' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" @@ -175,7 +176,7 @@ dependencies { //MarkDown implementation 'ru.noties.markwon:core:3.0.2' - // 转换繁体 + //转换繁体 implementation 'com.github.houbb:opencc4j:1.4.0' } diff --git a/app/google-services.json b/app/google-services.json index 8cc19b1d5..fb88090cb 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -63,6 +63,35 @@ ] } } + }, + { + "client_info": { + "mobilesdk_app_id": "1:453392274790:android:c1481c1c3d3f51eff624a7", + "android_client_info": { + "package_name": "io.legado.app.release" + } + }, + "oauth_client": [ + { + "client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } } ], "configuration_version": "1" diff --git a/app/src/debug/google-services.json b/app/src/debug/google-services.json deleted file mode 100644 index 8cc19b1d5..000000000 --- a/app/src/debug/google-services.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "project_info": { - "project_number": "453392274790", - "firebase_url": "https://legado-fca69.firebaseio.com", - "project_id": "legado-fca69", - "storage_bucket": "legado-fca69.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:453392274790:android:1d2b1eefbe0e78cff624a7", - "android_client_info": { - "package_name": "io.legado.app" - } - }, - "oauth_client": [ - { - "client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:453392274790:android:c4eac14b1410eec5f624a7", - "android_client_info": { - "package_name": "io.legado.app.debug" - } - }, - "oauth_client": [ - { - "client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a8ebd70da..e4bc75603 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -123,9 +123,6 @@ - @@ -135,24 +132,51 @@ - - + - + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/app/src/main/assets/txtTocRule.json b/app/src/main/assets/txtTocRule.json index 1b0651e76..4e93e04d7 100644 --- a/app/src/main/assets/txtTocRule.json +++ b/app/src/main/assets/txtTocRule.json @@ -1,56 +1,62 @@ [ { - "enable": true, - "name": "数字 分隔符 标题名称", - "rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$", - "serialNumber": 0 + "enable": true, + "name": "数字 分隔符 标题名称", + "rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$", + "serialNumber": 0 }, { - "enable": true, - "name": "特殊符号 序号 标题", - "rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(]?[第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[\\.:: \f\t].{0,30}$", - "serialNumber": 1 + "enable": true, + "name": "目录", + "rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", + "serialNumber": 1 }, { - "enable": true, - "name": "特殊符号 标题", - "rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$", - "serialNumber": 2 + "enable": false, + "name": "目录(不匹配行前空白)", + "rule": "^(?<=\\s)(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", + "serialNumber": 2 }, { - "enable": false, - "name": "特殊符号 标题(不匹配空白字符)", - "rule": "(?<=\\s)[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$", - "serialNumber": 3 + "enable": false, + "name": "目录(去简介)", + "rule": "^(?<=\\s)(?:前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", + "serialNumber": 3 }, { - "enable": true, - "name": "Chapter/Section/Part 序号 标题", - "rule": "^[ \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art)\\s{0,4}\\d{1,4}.{0,30}$", - "serialNumber": 4 + "enable": false, + "name": "目录(古典小说备用)", + "rule": "^[ \\t]{0,4}(?:前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|场(?![和合比电是])|篇(?!张))).{0,30}$", + "serialNumber": 4 }, { - "enable": true, - "name": "正文 标题/序号", - "rule": "^[ \\t]{0,4}正文\\s{1,4}.{0,20}$", - "serialNumber": 5 + "enable": true, + "name": "Chapter/Section/Part 序号 标题", + "rule": "^[ \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art)\\s{0,4}\\d{1,4}.{0,30}$", + "serialNumber": 5 }, { - "enable": true, - "name": "目录", - "rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", - "serialNumber": 6 + "enable": true, + "name": "正文 标题/序号", + "rule": "^[ \\t]{0,4}正文\\s{1,4}.{0,20}$", + "serialNumber": 6 }, { - "enable": false, - "name": "目录(不匹配行前空白,正文不出现双标题)", - "rule": "^(?<=\\s)(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", - "serialNumber": 7 + "enable": true, + "name": "特殊符号 序号 标题", + "rule": "^[ \\t]{0,4}[〈〖〔【][第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节][\\.:: \f\t].{0,30}$", + "serialNumber": 7 }, { - "enable": false, - "name": "目录(古典小说备用)", - "rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|场(?![和合比电是])|篇(?!张))).{0,30}$", - "serialNumber": 8 + "enable": true, + "name": "特殊符号 标题", + "rule": "^[ \\t]{0,4}[〈〖〔【☆★].{1,30}[】〕〗〉]?\\s{0,4}$", + "serialNumber": 8 + }, + { + "enable":false, + "name": "特殊符号 标题(不匹配空白字符)", + "rule": "(?<=\\s)[〈〖〔【☆★].{1,30}[】〕〗〉]?\\s{0,4}$", + "serialNumber": 9 } ] diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 697c57b4b..dea42f86d 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -2,6 +2,41 @@ * 旧版数据导入教程: * 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 +**2020/02/29** +* 添加书源一键导入 +* 修复主题模式跟随系统 +* 修复书源校验 +* 添加书架排序 + +**2020/02/28** +* 解决阅读界面部分字体超出范围的问题 +* 修复背景切换有时空白的bug +* 修复滚动翻页问题 + +**2020/02/27** +* 修复bug,边距调节,换源等一些bug,记不清了 +* 修复默认字体问题 +* 改了下包名,好上架应用市场 + +**2020/02/26** +* 修复仿真翻页 +* 功能添加: 选择默认字体时, 可选择字体默认字体(非衬线), 系统衬线字体, 系统等宽字体by hingbong + +**2020/02/25** +* 优化文本选择和滚动,感觉很完美了 + +**2020/02/24** +* 滚动暂时可以滚了,先这样吧,头大 +* 紧急修复朗读报错的bug + +**2020/02/23** +* 修复BUG +* 本地目录正则自定义完成 +* 选择文本修复框选不全的问题,增加操作按钮 + +**2020/02/22** +* 长按选择完成 + **2020/02/21** * 重写了阅读界面,实现了段距调整,两端对齐,页眉页脚调整 * 选择文本暂不可用,滚动暂不可用,仿真翻页还有问题 diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 7310dcef8..cf0f4cf43 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -5,6 +5,7 @@ import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import android.content.res.Configuration import android.os.Build import android.os.Bundle import androidx.annotation.RequiresApi @@ -62,6 +63,13 @@ class App : Application() { registerActivityLife() } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES, Configuration.UI_MODE_NIGHT_NO -> applyDayNight() + } + } + /** * 更新主题 */ @@ -91,7 +99,6 @@ class App : Application() { ) .apply() } -// ChapterProvider.upReadAloudSpan() } fun applyDayNight() { diff --git a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt index 8bb119625..e893e34c4 100644 --- a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt @@ -2,6 +2,7 @@ package io.legado.app.base import android.os.Bundle import androidx.fragment.app.DialogFragment +import io.legado.app.help.coroutine.Coroutine import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -21,4 +22,12 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { super.onDestroy() job.cancel() } + + fun execute( + scope: CoroutineScope = this, + context: CoroutineContext = Dispatchers.IO, + block: suspend CoroutineScope.() -> T + ): Coroutine { + return Coroutine.async(scope, context) { block() } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt index 25e6b25c4..f864aa4e8 100644 --- a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt +++ b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt @@ -293,6 +293,11 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : fun getItem(position: Int): ITEM? = if (position in 0 until items.size) items[position] else null + fun getItemByLayoutPosition(position: Int): ITEM? { + val pos = position - getHeaderCount() + return if (pos in 0 until items.size) items[pos] else null + } + fun getItems(): List = items protected open fun getItemViewType(item: ITEM, position: Int): Int { diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 320bc87ee..225154a97 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -16,6 +16,7 @@ object PreferKey { const val nextKey = "nextKeyCode" const val showRss = "showRss" const val bookshelfLayout = "bookshelfLayout" + const val bookshelfSort = "bookshelfSort" const val recordLog = "recordLog" const val processText = "process_text" const val cleanCache = "cleanCache" @@ -36,4 +37,7 @@ object PreferKey { const val textSelectAble = "selectText" const val lastBackup = "lastBackup" const val bodyIndent = "textIndent" + const val shareLayout = "shareLayout" + const val readStyleSelect = "readStyleSelect" + const val systemTypefaces = "system_typefaces" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt index 0981a94ea..9ce5c65be 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt @@ -14,19 +14,19 @@ interface BookSourceDao { @Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey order by customOrder asc") fun liveDataSearch(searchKey: String = ""): LiveData> - @Query("select * from book_sources where enabled = 1 and enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' order by customOrder asc") + @Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc") fun liveExplore(): LiveData> - @Query("select * from book_sources where enabled = 1 and enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc") + @Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc") fun liveExplore(key: String): LiveData> - @Query("select bookSourceGroup from book_sources where bookSourceGroup is not null and bookSourceGroup <> ''") + @Query("select bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''") fun liveGroup(): LiveData> - @Query("select bookSourceGroup from book_sources where enabled = 1 and bookSourceGroup is not null and bookSourceGroup <> ''") + @Query("select bookSourceGroup from book_sources where enabled = 1 and trim(bookSourceGroup) <> ''") fun liveGroupEnabled(): LiveData> - @Query("select bookSourceGroup from book_sources where enabled = 1 and enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' and bookSourceGroup is not null and bookSourceGroup <> ''") + @Query("select bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''") fun liveGroupExplore(): LiveData> @Query("select distinct enabled from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey") @@ -41,7 +41,7 @@ interface BookSourceDao { @Query("select * from book_sources where enabled = 1 and bookSourceGroup like '%' || :group || '%'") fun getEnabledByGroup(group: String): List - @get:Query("select * from book_sources where bookUrlPattern is not null || bookUrlPattern <> ''") + @get:Query("select * from book_sources where trim(bookUrlPattern) <> ''") val hasBookUrlPattern: List @get:Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = ''") diff --git a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt index c1c1e85d9..5436ee26c 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt @@ -28,7 +28,7 @@ interface RssSourceDao { @Query("SELECT * FROM rssSources where enabled = 1 order by customOrder") fun liveEnabled(): LiveData> - @Query("select sourceGroup from rssSources where sourceGroup is not null and sourceGroup <> ''") + @Query("select sourceGroup from rssSources where trim(sourceGroup) <> ''") fun liveGroup(): LiveData> @get:Query("select min(customOrder) from rssSources") diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt index 119daa651..5812b419c 100644 --- a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt @@ -1,11 +1,15 @@ package io.legado.app.data.dao +import androidx.lifecycle.LiveData import androidx.room.* import io.legado.app.data.entities.TxtTocRule @Dao interface TxtTocRuleDao { + @Query("select * from txtTocRules order by serialNumber") + fun observeAll(): LiveData> + @get:Query("select * from txtTocRules order by serialNumber") val all: List @@ -15,6 +19,10 @@ interface TxtTocRuleDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg rule: TxtTocRule) + @Update(onConflict = OnConflictStrategy.REPLACE) + fun update(vararg rule: TxtTocRule) + @Delete fun delete(vararg rule: TxtTocRule) + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/BaseBook.kt b/app/src/main/java/io/legado/app/data/entities/BaseBook.kt index 0c96c2123..62d322b6c 100644 --- a/app/src/main/java/io/legado/app/data/entities/BaseBook.kt +++ b/app/src/main/java/io/legado/app/data/entities/BaseBook.kt @@ -3,6 +3,7 @@ package io.legado.app.data.entities import io.legado.app.utils.splitNotBlank interface BaseBook { + var bookUrl: String var variableMap: HashMap? var kind: String? var wordCount: String? diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index d01919a94..d29ce696e 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -17,7 +17,7 @@ import kotlin.math.max @Entity(tableName = "books", indices = [(Index(value = ["bookUrl"], unique = true))]) data class Book( @PrimaryKey - var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) + override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) var tocUrl: String = "", // 目录页Url (toc=table of Contents) var origin: String = BookType.local, // 书源URL(默认BookType.local) var originName: String = "", //书源名称 or 本地书籍文件名 diff --git a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt index 2baf259e4..27f666547 100644 --- a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt +++ b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt @@ -21,7 +21,7 @@ import kotlinx.android.parcel.Parcelize ) data class SearchBook( @PrimaryKey - var bookUrl: String = "", + override var bookUrl: String = "", var origin: String = "", // 书源规则 var originName: String = "", var type: Int = 0, // @BookType @@ -78,16 +78,12 @@ data class SearchBook( variable = GSON.toJson(variableMap) } - @Ignore + @delegate:Ignore @IgnoredOnParcel - var origins: LinkedHashSet? = null - private set + val origins: LinkedHashSet by lazy { linkedSetOf(origin) } fun addOrigin(origin: String) { - if (origins == null) { - origins = linkedSetOf(this.origin) - } - origins?.add(origin) + origins.add(origin) } fun getDisplayLastChapterTitle(): String { diff --git a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt index c9cd51d4d..cdfca7567 100644 --- a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt @@ -9,6 +9,6 @@ data class TxtTocRule( @PrimaryKey var name: String = "", var rule: String = "", - var serialNumber: Int, + var serialNumber: Int = -1, var enable: Boolean = true ) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index 1f4302fdc..f71652c23 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -86,4 +86,10 @@ object AppConfig { set(value) { App.INSTANCE.putPrefInt(PreferKey.chineseConverterType, value) } + + var systemTypefaces: Int + get() = App.INSTANCE.getPrefInt(PreferKey.systemTypefaces) + set(value) { + App.INSTANCE.putPrefInt(PreferKey.systemTypefaces, value) + } } diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index e474bff96..c7560b819 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -237,8 +237,14 @@ object BookHelp { private var bookName: String? = null private var bookOrigin: String? = null private var replaceRules: List = arrayListOf() - val bodyIndent - get() = " ".repeat(App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)) + var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) + set(value) { + field = value + App.INSTANCE.putPrefInt(PreferKey.bodyIndent, value) + bodyIndent = " ".repeat(value) + } + var bodyIndent = " ".repeat(bodyIndentCount) + fun disposeContent( title: String, @@ -247,7 +253,6 @@ object BookHelp { content: String, enableReplace: Boolean ): String { - var c = content synchronized(this) { if (enableReplace && (bookName != name || bookOrigin != origin)) { replaceRules = if (origin.isNullOrEmpty()) { @@ -257,6 +262,7 @@ object BookHelp { } } } + var c = content for (item in replaceRules) { item.pattern.let { if (it.isNotEmpty()) { @@ -268,8 +274,8 @@ object BookHelp { } } } - if (!content.substringBefore("\n").contains(title)) { - c = title + "\n" + c + if (!c.substringBefore("\n").contains(title)) { + c = "$title\n$c" } when (AppConfig.chineseConverterType) { 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) diff --git a/app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt b/app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt deleted file mode 100644 index a35f0cd8c..000000000 --- a/app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.legado.app.help - -import androidx.recyclerview.widget.ListUpdateCallback -import androidx.recyclerview.widget.RecyclerView -import io.legado.app.base.adapter.ItemViewHolder - -class FirstTopListUpCallback : ListUpdateCallback { - var firstInsert = -1 - lateinit var adapter: RecyclerView.Adapter - - override fun onChanged(position: Int, count: Int, payload: Any?) { - adapter.notifyItemRangeChanged(position, count, payload) - } - - override fun onMoved(fromPosition: Int, toPosition: Int) { - if (toPosition == 0) { - firstInsert = 0 - } - adapter.notifyItemMoved(fromPosition, toPosition) - } - - override fun onInserted(position: Int, count: Int) { - if (firstInsert == -1 || firstInsert > position) { - firstInsert = position - } - adapter.notifyItemRangeInserted(position, count) - } - - override fun onRemoved(position: Int, count: Int) { - adapter.notifyItemRangeRemoved(position, count) - } - - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt b/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt index 0e9a29351..99ba74ef1 100644 --- a/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt +++ b/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt @@ -128,7 +128,7 @@ class ItemTouchCallback : ItemTouchHelper.Callback() { * @return 开发者处理了操作应该返回true,开发者没有处理就返回false */ fun onMove(srcPosition: Int, targetPosition: Int): Boolean { - return false + return true } fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index 5dc2d4817..57ccd5f14 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -6,6 +6,7 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import io.legado.app.App import io.legado.app.R +import io.legado.app.constant.PreferKey import io.legado.app.help.coroutine.Coroutine import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.utils.* @@ -23,12 +24,25 @@ object ReadBookConfig { val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes()) GSON.fromJsonArray(json)!! } - val durConfig - get() = getConfig(styleSelect) - - var styleSelect - get() = App.INSTANCE.getPrefInt("readStyleSelect") - set(value) = App.INSTANCE.putPrefInt("readStyleSelect", value) + val durConfig get() = getConfig(styleSelect) + private val shareConfig get() = getConfig(5) + var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect) + set(value) { + field = value + App.INSTANCE.putPrefInt(PreferKey.readStyleSelect, value) + } + var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout) + set(value) { + field = value + App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value) + } + var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim) + set(value) { + field = value + isScroll = value == 3 + App.INSTANCE.putPrefInt(PreferKey.pageAnim, value) + } + var isScroll = pageAnim == 3 var bg: Drawable? = null init { @@ -40,6 +54,9 @@ object ReadBookConfig { if (configList.size < 5) { resetAll() } + if (configList.size < 6) { + configList.add(Config()) + } return configList[index] } @@ -94,20 +111,122 @@ object ReadBookConfig { } } - data class Config( - var bgStr: String = "#EEEEEE",//白天背景 - var bgStrNight: String = "#000000",//夜间背景 - var bgType: Int = 0,//白天背景类型 - var bgTypeNight: Int = 0,//夜间背景类型 - var darkStatusIcon: Boolean = true,//白天是否暗色状态栏 - var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏 - var textColor: String = "#3E3D3B",//白天文字颜色 - var textColorNight: String = "#adadad",//夜间文字颜色 + //配置写入读取 + var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) + var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) + var textBold: Boolean + get() = if (shareLayout) shareConfig.textBold else durConfig.textBold + set(value) = if (shareLayout) shareConfig.textBold = value else durConfig.textBold = value + + var textSize: Int + get() = if (shareLayout) shareConfig.textSize else durConfig.textSize + set(value) = if (shareLayout) shareConfig.textSize = value else durConfig.textSize = value + + var letterSpacing: Float + get() = if (shareLayout) shareConfig.letterSpacing else durConfig.letterSpacing + set(value) = + if (shareLayout) shareConfig.letterSpacing = value else durConfig.letterSpacing = value + + var lineSpacingExtra: Int + get() = if (shareLayout) shareConfig.lineSpacingExtra else durConfig.lineSpacingExtra + set(value) = + if (shareLayout) shareConfig.lineSpacingExtra = value + else durConfig.lineSpacingExtra = value + + var paragraphSpacing: Int + get() = if (shareLayout) shareConfig.paragraphSpacing else durConfig.paragraphSpacing + set(value) = + if (shareLayout) shareConfig.paragraphSpacing = value + else durConfig.paragraphSpacing = value + + var titleCenter: Boolean + get() = if (shareLayout) shareConfig.titleCenter else durConfig.titleCenter + set(value) = + if (shareLayout) shareConfig.titleCenter = value else durConfig.titleCenter = value + + var paddingBottom: Int + get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom + set(value) = + if (shareLayout) shareConfig.paddingBottom = value else durConfig.paddingBottom = value + + var paddingLeft: Int + get() = if (shareLayout) shareConfig.paddingLeft else durConfig.paddingLeft + set(value) = + if (shareLayout) shareConfig.paddingLeft = value else durConfig.paddingLeft = value + + var paddingRight: Int + get() = if (shareLayout) shareConfig.paddingRight else durConfig.paddingRight + set(value) = + if (shareLayout) shareConfig.paddingRight = value else durConfig.paddingRight = value + + var paddingTop: Int + get() = if (shareLayout) shareConfig.paddingTop else durConfig.paddingTop + set(value) = + if (shareLayout) shareConfig.paddingTop = value else durConfig.paddingTop = value + + var headerPaddingBottom: Int + get() = if (shareLayout) shareConfig.headerPaddingBottom else durConfig.headerPaddingBottom + set(value) = + if (shareLayout) shareConfig.headerPaddingBottom = value + else durConfig.headerPaddingBottom = value + + var headerPaddingLeft: Int + get() = if (shareLayout) shareConfig.headerPaddingLeft else durConfig.headerPaddingLeft + set(value) = + if (shareLayout) shareConfig.headerPaddingLeft = value + else durConfig.headerPaddingLeft = value + + var headerPaddingRight: Int + get() = if (shareLayout) shareConfig.headerPaddingRight else durConfig.headerPaddingRight + set(value) = + if (shareLayout) shareConfig.headerPaddingRight = value + else durConfig.headerPaddingRight = value + + var headerPaddingTop: Int + get() = if (shareLayout) shareConfig.headerPaddingTop else durConfig.headerPaddingTop + set(value) = + if (shareLayout) shareConfig.headerPaddingTop = value + else durConfig.headerPaddingTop = value + + var footerPaddingBottom: Int + get() = if (shareLayout) shareConfig.footerPaddingBottom else durConfig.footerPaddingBottom + set(value) = + if (shareLayout) shareConfig.footerPaddingBottom = value + else durConfig.footerPaddingBottom = value + + var footerPaddingLeft: Int + get() = if (shareLayout) shareConfig.footerPaddingLeft else durConfig.footerPaddingLeft + set(value) = + if (shareLayout) shareConfig.footerPaddingLeft = value + else durConfig.footerPaddingLeft = value + + var footerPaddingRight: Int + get() = if (shareLayout) shareConfig.footerPaddingRight else durConfig.footerPaddingRight + set(value) = + if (shareLayout) shareConfig.footerPaddingRight = value + else durConfig.footerPaddingRight = value + + var footerPaddingTop: Int + get() = if (shareLayout) shareConfig.footerPaddingTop else durConfig.footerPaddingTop + set(value) = + if (shareLayout) shareConfig.footerPaddingTop = value + else durConfig.footerPaddingTop = value + + class Config( + private var bgStr: String = "#EEEEEE",//白天背景 + private var bgStrNight: String = "#000000",//夜间背景 + private var bgType: Int = 0,//白天背景类型 0:颜色, 1:assets图片, 2其它图片 + private var bgTypeNight: Int = 0,//夜间背景类型 + private var darkStatusIcon: Boolean = true,//白天是否暗色状态栏 + private var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏 + private var textColor: String = "#3E3D3B",//白天文字颜色 + private var textColorNight: String = "#ADADAD",//夜间文字颜色 var textBold: Boolean = false,//是否粗体字 - var textSize: Int = 15,//文字大小 + var textSize: Int = 20,//文字大小 var letterSpacing: Float = 1f,//字间距 var lineSpacingExtra: Int = 12,//行间距 - var paragraphSpacing: Int = 12, + var paragraphSpacing: Int = 12,//段距 + var titleCenter: Boolean = true,//标题居中 var paddingBottom: Int = 6, var paddingLeft: Int = 16, var paddingRight: Int = 16, @@ -137,7 +256,7 @@ object ReadBookConfig { } else { textColor = "#${color.hexString}" } - ChapterProvider.upStyle(this) + ChapterProvider.upStyle() } fun setStatusIconDark(isDark: Boolean) { @@ -180,7 +299,7 @@ object ReadBookConfig { 1 -> { BitmapDrawable( resources, - BitmapUtils.decodeBitmap( + BitmapUtils.decodeAssetsBitmap( App.INSTANCE, "bg" + File.separator + bgStr(), width, diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index c7de266ca..921910f34 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -100,13 +100,9 @@ object Backup { for (fileName in backupFileNames) { val file = File(backupPath + File.separator + fileName) if (file.exists()) { - val doc = treeDoc.findFile(fileName) ?: treeDoc.createFile("", fileName) - doc?.let { - DocumentUtils.writeText( - context, - file.readText(), - doc.uri - ) + treeDoc.findFile(fileName)?.delete() + treeDoc.createFile("", fileName)?.let { + DocumentUtils.writeBytes(context, file.readBytes(), it.uri) } } } diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index 123332df6..dd48d6472 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -12,6 +12,8 @@ import io.legado.app.constant.PreferKey import io.legado.app.data.entities.* import io.legado.app.help.LauncherIconHelp import io.legado.app.help.ReadBookConfig +import io.legado.app.service.help.ReadBook +import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.withContext @@ -104,8 +106,17 @@ object Restore { else -> Unit } edit.putInt(PreferKey.versionCode, App.INSTANCE.versionCode) - edit.commit() + edit.apply() } + ReadBookConfig.apply { + styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect) + shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout) + pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim) + hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) + hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) + } + ChapterProvider.upStyle() + ReadBook.loadContent() } LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) } diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 4cc2544f5..bb3f80b8d 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -86,17 +86,18 @@ class WebBook(val bookSource: BookSource) { ): Coroutine { book.type = bookSource.bookSourceType return Coroutine.async(scope, context) { - val body = if (!book.infoHtml.isNullOrEmpty()) { - book.infoHtml - } else { - val analyzeUrl = AnalyzeUrl( - book = book, - ruleUrl = book.bookUrl, - baseUrl = sourceUrl, - headerMapF = bookSource.getHeaderMap() - ) - analyzeUrl.getResponseAwait().body - } + val body = + if (!book.infoHtml.isNullOrEmpty()) { + book.infoHtml + } else { + val analyzeUrl = AnalyzeUrl( + book = book, + ruleUrl = book.bookUrl, + baseUrl = sourceUrl, + headerMapF = bookSource.getHeaderMap() + ) + analyzeUrl.getResponseAwait().body + } BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl) book } @@ -112,16 +113,17 @@ class WebBook(val bookSource: BookSource) { ): Coroutine> { book.type = bookSource.bookSourceType return Coroutine.async(scope, context) { - val body = if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { - book.tocHtml - } else { - AnalyzeUrl( - book = book, - ruleUrl = book.tocUrl, - baseUrl = book.bookUrl, - headerMapF = bookSource.getHeaderMap() - ).getResponseAwait().body - } + val body = + if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { + book.tocHtml + } else { + AnalyzeUrl( + book = book, + ruleUrl = book.tocUrl, + baseUrl = book.bookUrl, + headerMapF = bookSource.getHeaderMap() + ).getResponseAwait().body + } BookChapterList.analyzeChapterList(this, book, body, bookSource, book.tocUrl) } } @@ -141,22 +143,23 @@ class WebBook(val bookSource: BookSource) { Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") return@async bookChapter.url } - val body = if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { - book.tocHtml - } else { - val analyzeUrl = - AnalyzeUrl( - book = book, - ruleUrl = bookChapter.url, - baseUrl = book.tocUrl, - headerMapF = bookSource.getHeaderMap() - ) - analyzeUrl.getResponseAwait( - bookSource.bookSourceUrl, - jsStr = bookSource.getContentRule().webJs, - sourceRegex = bookSource.getContentRule().sourceRegex - ).body - } + val body = + if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { + book.tocHtml + } else { + val analyzeUrl = + AnalyzeUrl( + book = book, + ruleUrl = bookChapter.url, + baseUrl = book.tocUrl, + headerMapF = bookSource.getHeaderMap() + ) + analyzeUrl.getResponseAwait( + bookSource.bookSourceUrl, + jsStr = bookSource.getContentRule().webJs, + sourceRegex = bookSource.getContentRule().sourceRegex + ).body + } BookContent.analyzeContent( this, body, diff --git a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt index 99b6b95d3..fad747b34 100644 --- a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt @@ -205,6 +205,9 @@ object AnalyzeTxtFile { } private fun getTocRule(book: Book, bookStream: RandomAccessFile, charset: Charset): Pattern? { + if (book.tocUrl.isNotEmpty()) { + return Pattern.compile(book.tocUrl, Pattern.MULTILINE) + } val tocRules = getTocRules() var rulePattern: Pattern? = null //首先获取128k的数据 @@ -227,13 +230,18 @@ object AnalyzeTxtFile { private fun getTocRules(): List { val rules = App.db.txtTocRule().all if (rules.isEmpty()) { - App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> - GSON.fromJsonArray(String(byteArray))?.let { - App.db.txtTocRule().insert(*it.toTypedArray()) - return it - } - } + return getDefaultRules() } return rules } + + fun getDefaultRules(): List { + App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> + GSON.fromJsonArray(String(byteArray))?.let { + App.db.txtTocRule().insert(*it.toTypedArray()) + return it + } + } + return emptyList() + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index ff3467a8a..aa0b7de74 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -31,6 +31,7 @@ object BookChapterList { App.INSTANCE.getString(R.string.error_get_web_content, baseUrl) ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") + val analyzeRule = AnalyzeRule(book) val tocRule = bookSource.getTocRule() val nextUrlList = arrayListOf(baseUrl) var reverse = false @@ -43,7 +44,10 @@ object BookChapterList { listRule = listRule.substring(1) } var chapterData = - analyzeChapterList(body, baseUrl, tocRule, listRule, book, bookSource, log = true) + analyzeChapterList( + analyzeRule.setContent(body, baseUrl), + book.bookUrl, baseUrl, tocRule, listRule, bookSource, log = true + ) chapterData.chapterList?.let { chapterList.addAll(it) } @@ -63,8 +67,8 @@ object BookChapterList { ).getResponseAwait() .body?.let { nextBody -> chapterData = analyzeChapterList( - nextBody, nextUrl, tocRule, listRule, - book, bookSource, log = false + analyzeRule.setContent(nextBody, nextUrl), + book.bookUrl, nextUrl, tocRule, listRule, bookSource ) nextUrl = if (chapterData.nextUrl.isNotEmpty()) { chapterData.nextUrl[0] @@ -98,7 +102,9 @@ object BookChapterList { headerMapF = bookSource.getHeaderMap() ).getResponseAwait().body val nextChapterData = analyzeChapterList( - nextBody, item.nextUrl, tocRule, listRule, book, bookSource + analyzeRule.setContent(nextBody, item.nextUrl), + book.bookUrl, item.nextUrl, tocRule, listRule, bookSource, + false ) synchronized(chapterDataList) { val isFinished = addChapterListIsFinish( @@ -167,19 +173,17 @@ object BookChapterList { } private fun analyzeChapterList( - body: String?, + analyzeRule: AnalyzeRule, + bookUrl: String, baseUrl: String, tocRule: TocRule, listRule: String, - book: Book, bookSource: BookSource, getNextUrl: Boolean = true, log: Boolean = false ): ChapterData> { val chapterList = arrayListOf() val nextUrlList = arrayListOf() - val analyzeRule = AnalyzeRule(book) - analyzeRule.setContent(body, baseUrl) val nextTocRule = tocRule.nextTocUrl if (getNextUrl && !nextTocRule.isNullOrEmpty()) { Debug.log(bookSource.bookSourceUrl, "┌获取目录下一页列表", log) @@ -208,7 +212,7 @@ object BookChapterList { var isVip: String? for (item in elements) { analyzeRule.setContent(item) - val bookChapter = BookChapter(bookUrl = book.bookUrl) + val bookChapter = BookChapter(bookUrl = bookUrl) analyzeRule.chapter = bookChapter bookChapter.title = analyzeRule.getString(nameRule) bookChapter.url = analyzeRule.getString(urlRule, true) diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index b9455e6ff..d8e874195 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -33,11 +33,15 @@ object BookContent { ) ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") + val analyzeRule = AnalyzeRule(book) val content = StringBuilder() val nextUrlList = arrayListOf(baseUrl) val contentRule = bookSource.getContentRule() - var contentData = analyzeContent(body, contentRule, book, bookChapter, bookSource, baseUrl) - content.append(contentData.content) + var contentData = analyzeContent( + analyzeRule.setContent(body, baseUrl), + contentRule, bookChapter, bookSource + ) + content.append(contentData.content.replace(bookChapter.title, "")) if (contentData.nextUrl.size == 1) { var nextUrl = contentData.nextUrl[0] val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty()) @@ -56,15 +60,15 @@ object BookContent { headerMapF = bookSource.getHeaderMap() ).getResponseAwait() .body?.let { nextBody -> - contentData = - analyzeContent( - nextBody, contentRule, book, - bookChapter, bookSource, baseUrl, false - ) - nextUrl = - if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else "" - content.append(contentData.content) - } + contentData = + analyzeContent( + analyzeRule.setContent(nextBody, nextUrl), + contentRule, bookChapter, bookSource, false + ) + nextUrl = + if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else "" + content.append(contentData.content) + } } Debug.log(bookSource.bookSourceUrl, "◇本章总页数:${nextUrlList.size}") } else if (contentData.nextUrl.size > 1) { @@ -81,19 +85,20 @@ object BookContent { headerMapF = bookSource.getHeaderMap() ).getResponseAwait() .body?.let { - contentData = - analyzeContent( - it, contentRule, book, bookChapter, - bookSource, item.nextUrl, false - ) - item.content = contentData.content - } + contentData = + analyzeContent( + analyzeRule.setContent(it, item.nextUrl), + contentRule, bookChapter, bookSource, false + ) + item.content = contentData.content + } } } for (item in contentDataList) { content.append(item.content) } } + Debug.log(bookSource.bookSourceUrl, "┌获取章节名称") Debug.log(bookSource.bookSourceUrl, "└${bookChapter.title}") Debug.log(bookSource.bookSourceUrl, "┌获取正文内容") @@ -103,17 +108,13 @@ object BookContent { @Throws(Exception::class) private fun analyzeContent( - body: String, + analyzeRule: AnalyzeRule, contentRule: ContentRule, - book: Book, chapter: BookChapter, bookSource: BookSource, - baseUrl: String, printLog: Boolean = true ): ContentData> { val nextUrlList = arrayListOf() - val analyzeRule = AnalyzeRule(book) - analyzeRule.setContent(body, baseUrl) analyzeRule.chapter = chapter val nextUrlRule = contentRule.nextContentUrl if (!nextUrlRule.isNullOrEmpty()) { diff --git a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt index 12083c652..00c9d0522 100644 --- a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt +++ b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt @@ -45,7 +45,7 @@ class MediaButtonReceiver : BroadcastReceiver() { } } } - return false + return true } private fun readAloud(context: Context) { diff --git a/app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt b/app/src/main/java/io/legado/app/receiver/TimeBatteryReceiver.kt similarity index 85% rename from app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt rename to app/src/main/java/io/legado/app/receiver/TimeBatteryReceiver.kt index 765d05b3e..ce2e04fbe 100644 --- a/app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt +++ b/app/src/main/java/io/legado/app/receiver/TimeBatteryReceiver.kt @@ -9,12 +9,12 @@ import io.legado.app.constant.EventBus import io.legado.app.utils.postEvent -class TimeElectricityReceiver : BroadcastReceiver() { +class TimeBatteryReceiver : BroadcastReceiver() { companion object { - fun register(context: Context): TimeElectricityReceiver { - val receiver = TimeElectricityReceiver() + fun register(context: Context): TimeBatteryReceiver { + val receiver = TimeBatteryReceiver() val filter = IntentFilter() filter.addAction(Intent.ACTION_TIME_TICK) filter.addAction(Intent.ACTION_BATTERY_CHANGED) diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index 18f704a6e..d94b3a6d1 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -14,14 +14,15 @@ import io.legado.app.model.WebBook import io.legado.app.ui.book.source.manage.BookSourceActivity import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher +import org.jetbrains.anko.toast import java.util.concurrent.Executors class CheckSourceService : BaseService() { private var searchPool = Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() private var task: Coroutine<*>? = null - private var idsCount = 0 - private val unCheckIds = LinkedHashSet() + private val allIds = LinkedHashSet() + private val checkedIds = LinkedHashSet() override fun onCreate() { super.onCreate() @@ -46,12 +47,12 @@ class CheckSourceService : BaseService() { private fun check(ids: List) { task?.cancel() - unCheckIds.clear() - idsCount = ids.size - unCheckIds.addAll(ids) - updateNotification(0, getString(R.string.progress_show, 0, idsCount)) - task = execute { - unCheckIds.forEach { sourceUrl -> + allIds.clear() + checkedIds.clear() + allIds.addAll(ids) + updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) + task = execute(context = searchPool) { + allIds.forEach { sourceUrl -> App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> val webBook = WebBook(source) webBook.searchBook("我的", scope = this, context = searchPool) @@ -59,15 +60,16 @@ class CheckSourceService : BaseService() { source.addGroup("失效") App.db.bookSourceDao().update(source) }.onFinally { - unCheckIds.remove(sourceUrl) - val checkedCount = idsCount - unCheckIds.size + checkedIds.add(sourceUrl) updateNotification( - checkedCount, - getString(R.string.progress_show, checkedCount, idsCount) + checkedIds.size, + getString(R.string.progress_show, checkedIds.size, allIds.size) ) } } } + }.onError { + toast("校验书源出错:${it.localizedMessage}") } task?.invokeOnCompletion { @@ -92,7 +94,7 @@ class CheckSourceService : BaseService() { getString(R.string.cancel), IntentHelp.servicePendingIntent(this, IntentAction.stop) ) - builder.setProgress(idsCount, state, false) + builder.setProgress(allIds.size, state, false) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) val notification = builder.build() startForeground(112202, notification) diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index 2cf46831c..9b4b56ebe 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -5,16 +5,15 @@ import android.media.MediaPlayer import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.help.IntentHelp +import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.api.HttpPostApi import io.legado.app.service.help.ReadBook import io.legado.app.utils.FileUtils import io.legado.app.utils.LogUtils import io.legado.app.utils.postEvent -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Job import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch +import org.jetbrains.anko.toast import java.io.File import java.io.FileDescriptor import java.io.FileInputStream @@ -27,7 +26,7 @@ class HttpReadAloudService : BaseReadAloudService(), private val mediaPlayer = MediaPlayer() private lateinit var ttsFolder: String - private var job: Job? = null + private var task: Coroutine<*>? = null private var playingIndex = -1 override fun onCreate() { @@ -40,13 +39,12 @@ class HttpReadAloudService : BaseReadAloudService(), override fun onDestroy() { super.onDestroy() - job?.cancel() + task?.cancel() mediaPlayer.release() } override fun newReadAloud(dataKey: String?, play: Boolean) { mediaPlayer.reset() - job?.cancel() playingIndex = -1 super.newReadAloud(dataKey, play) } @@ -64,7 +62,8 @@ class HttpReadAloudService : BaseReadAloudService(), } private fun downloadAudio() { - job = launch(IO) { + task?.cancel() + task = execute { FileUtils.deleteFile(ttsFolder) for (index in 0 until contentList.size) { if (isActive) { @@ -85,6 +84,8 @@ class HttpReadAloudService : BaseReadAloudService(), break } } + }.onError { + toast("下载朗读文件出错:${it.localizedMessage}") } } @@ -150,7 +151,7 @@ class HttpReadAloudService : BaseReadAloudService(), * 更新朗读速度 */ override fun upSpeechRate(reset: Boolean) { - job?.cancel() + task?.cancel() mediaPlayer.stop() playingIndex = -1 downloadAudio() diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index 41bfa3c77..207c34ec6 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.jetbrains.anko.toast object ReadBook { @@ -25,10 +26,10 @@ object ReadBook { var book: Book? = null var inBookshelf = false var chapterSize = 0 - var callBack: CallBack? = null var durChapterIndex = 0 var durPageIndex = 0 var isLocalBook = true + var callBack: CallBack? = null var prevTextChapter: TextChapter? = null var curTextChapter: TextChapter? = null var nextTextChapter: TextChapter? = null @@ -126,7 +127,13 @@ object ReadBook { saveRead() } - fun curPageChanged() { + fun setPageIndex(pageIndex: Int) { + durPageIndex = pageIndex + saveRead() + curPageChanged() + } + + private fun curPageChanged() { callBack?.upPageProgress() if (BaseReadAloudService.isRun) { readAloud(!BaseReadAloudService.pause) @@ -283,6 +290,9 @@ object ReadBook { } } } + }.onError { + it.printStackTrace() + App.INSTANCE.toast(it.localizedMessage ?: "ChapterProvider ERROR") } } @@ -302,7 +312,7 @@ object ReadBook { } interface CallBack { - fun upContent(position: Int = 0) + fun upContent(relativePosition: Int = 0) fun upView() fun upPageProgress() fun contentLoadFinish() diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt index 18e3bcdb9..8fc7d1294 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt @@ -1,14 +1,12 @@ package io.legado.app.ui.about -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem import io.legado.app.R import io.legado.app.base.BaseActivity -import io.legado.app.utils.shareText -import org.jetbrains.anko.toast +import io.legado.app.utils.openUrl +import org.jetbrains.anko.share class AboutActivity : BaseActivity(R.layout.activity_about) { @@ -28,22 +26,13 @@ class AboutActivity : BaseActivity(R.layout.activity_about) { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.menu_scoring -> openIntent("market://details?id=$packageName") - R.id.menu_share_it -> shareText( - "App Share", - getString(R.string.app_share_description) + R.id.menu_scoring -> openUrl("market://details?id=$packageName") + R.id.menu_share_it -> share( + getString(R.string.app_share_description), + getString(R.string.app_name) ) } return super.onCompatOptionsItemSelected(item) } - private fun openIntent(address: String) { - try { - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(address) - startActivity(intent) - } catch (e: Exception) { - toast(R.string.can_not_open) - } - } } diff --git a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt index 454fda8e9..365381fbd 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt @@ -1,8 +1,5 @@ package io.legado.app.ui.about -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle @@ -14,6 +11,8 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.lib.dialogs.alert import io.legado.app.ui.widget.dialog.TextDialog +import io.legado.app.utils.openUrl +import io.legado.app.utils.sendToClip import io.legado.app.utils.toast class AboutFragment : PreferenceFragmentCompat() { @@ -43,32 +42,32 @@ class AboutFragment : PreferenceFragmentCompat() { override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { - "contributors" -> openIntent(Intent.ACTION_VIEW, R.string.contributors_url) + "contributors" -> openUrl(R.string.contributors_url) "update_log" -> showUpdateLog() - "check_update" -> openIntent(Intent.ACTION_VIEW, R.string.latest_release_url) - "mail" -> openIntent(Intent.ACTION_SENDTO, "mailto:kunfei.ge@gmail.com") - "git" -> openIntent(Intent.ACTION_VIEW, R.string.this_github_url) - "home_page" -> openIntent(Intent.ACTION_VIEW, R.string.home_page_url) - "license" -> openIntent(Intent.ACTION_VIEW, licenseUrl) - "disclaimer" -> openIntent(Intent.ACTION_VIEW, disclaimerUrl) + "check_update" -> openUrl(R.string.latest_release_url) + "mail" -> sendMail() + "git" -> openUrl(R.string.this_github_url) + "home_page" -> openUrl(R.string.home_page_url) + "license" -> requireContext().openUrl(licenseUrl) + "disclaimer" -> requireContext().openUrl(disclaimerUrl) "qq" -> showQqGroups() - "gzGzh" -> sendToClip("开源阅读软件") + "gzGzh" -> requireContext().sendToClip("开源阅读软件") } return super.onPreferenceTreeClick(preference) } @Suppress("SameParameterValue") - private fun openIntent(intentName: String, @StringRes addressID: Int) { - openIntent(intentName, getString(addressID)) + private fun openUrl(@StringRes addressID: Int) { + requireContext().openUrl(getString(addressID)) } - private fun openIntent(intentName: String, address: String) { + private fun sendMail() { try { - val intent = Intent(intentName) - intent.data = Uri.parse(address) + val intent = Intent(Intent.ACTION_SENDTO) + intent.data = Uri.parse("mailto:kunfei.ge@gmail.com") startActivity(intent) } catch (e: Exception) { - toast(R.string.can_not_open) + toast(e.localizedMessage ?: "Error") } } @@ -86,7 +85,7 @@ class AboutFragment : PreferenceFragmentCompat() { items(names) { _, index -> qqGroups[names[index]]?.let { if (!joinQQGroup(it)) { - sendToClip(it) + requireContext().sendToClip(it) } } } @@ -107,13 +106,4 @@ class AboutFragment : PreferenceFragmentCompat() { } } - private fun sendToClip(text: String) { - val clipboard = - requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - val clipData = ClipData.newPlainText(null, text) - clipboard?.let { - clipboard.setPrimaryClip(clipData) - toast(R.string.copy_complete) - } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt index a202946b6..0270b3f77 100644 --- a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt @@ -1,7 +1,5 @@ package io.legado.app.ui.about -import android.content.ClipData -import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri @@ -11,7 +9,8 @@ import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import io.legado.app.R import io.legado.app.utils.ACache -import io.legado.app.utils.toast +import io.legado.app.utils.openUrl +import io.legado.app.utils.sendToClip import org.jetbrains.anko.longToast import java.net.URLEncoder @@ -33,40 +32,19 @@ class DonateFragment : PreferenceFragmentCompat() { override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { - "wxZsm" -> openIntent(Intent.ACTION_VIEW, wxZsRwmUrl) - "zfbHbRwm" -> openIntent(Intent.ACTION_VIEW, zfbHbRwmUrl) - "zfbSkRwm" -> openIntent(Intent.ACTION_VIEW, zfbSkRwmUrl) - "qqSkRwm" -> openIntent(Intent.ACTION_VIEW, qqSkRwmUrl) + "wxZsm" -> requireContext().openUrl(wxZsRwmUrl) + "zfbHbRwm" -> requireContext().openUrl(zfbHbRwmUrl) + "zfbSkRwm" -> requireContext().openUrl(zfbSkRwmUrl) + "qqSkRwm" -> requireContext().openUrl(qqSkRwmUrl) "zfbSk" -> aliDonate(requireContext()) "zfbHbSsm" -> getZfbHb(requireContext()) - "gzGzh" -> sendToClip("开源阅读软件") + "gzGzh" -> requireContext().sendToClip("开源阅读软件") } return super.onPreferenceTreeClick(preference) } - @Suppress("SameParameterValue") - private fun openIntent(intentName: String, address: String) { - try { - val intent = Intent(intentName) - intent.data = Uri.parse(address) - startActivity(intent) - } catch (e: Exception) { - toast(R.string.can_not_open) - } - } - - private fun sendToClip(text: String) { - val clipboard = - requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - val clipData = ClipData.newPlainText(null, text) - clipboard?.let { - clipboard.setPrimaryClip(clipData) - toast(R.string.copy_complete) - } - } - private fun getZfbHb(context: Context) { - sendToClip("537954522") + requireContext().sendToClip("537954522") context.longToast("高级功能已开启\n红包码已复制\n支付宝首页搜索“537954522” 立即领红包") try { val packageManager = context.applicationContext.packageManager diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt index e3ca00964..c0907f002 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -19,8 +19,8 @@ import io.legado.app.lib.theme.ATH import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint -import io.legado.app.utils.getVerticalDivider import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_arrange_book.* @@ -60,7 +60,7 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi private fun initView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) adapter = ArrangeBookAdapter(this, this) recycler_view.adapter = adapter select_action_bar.setMainActionText(R.string.move_to_group) @@ -95,12 +95,14 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi private fun initBookData() { booksLiveData?.removeObservers(this) booksLiveData = - if (groupId == -1) { - App.db.bookDao().observeAll() - } else { - App.db.bookDao().observeByGroup(groupId) + when (groupId) { + -1 -> App.db.bookDao().observeAll() + -2 -> App.db.bookDao().observeLocal() + -3 -> App.db.bookDao().observeAudio() + else -> App.db.bookDao().observeByGroup(groupId) } booksLiveData?.observe(this, Observer { + adapter.selectedBooks.clear() adapter.setItems(it) upSelectCount() }) diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index e982cbbe0..1a4322da6 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -28,8 +28,8 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint -import io.legado.app.utils.getVerticalDivider import io.legado.app.utils.getViewModel import io.legado.app.utils.requestInputMethod import kotlinx.android.synthetic.main.dialog_edit_text.view.* @@ -77,7 +77,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { .isChecked = AppConst.bookGroupAudioShow adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { val diffResult = diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt index 2d90a82e2..b290bc5df 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt @@ -28,8 +28,8 @@ import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.theme.accentColor +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint -import io.legado.app.utils.getVerticalDivider import io.legado.app.utils.getViewModel import io.legado.app.utils.requestInputMethod import kotlinx.android.synthetic.main.dialog_book_group_picker.* @@ -98,7 +98,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tool_bar.menu.findItem(R.id.menu_group_audio).isVisible = false adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter val itemTouchCallback = ItemTouchCallback() itemTouchCallback.onItemTouchCallbackListener = adapter diff --git a/app/src/main/java/io/legado/app/ui/book/read/Help.kt b/app/src/main/java/io/legado/app/ui/book/read/Help.kt index 3a3e04191..549b7b987 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/Help.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/Help.kt @@ -10,7 +10,6 @@ import android.view.View.NO_ID import android.widget.EditText import io.legado.app.App import io.legado.app.R -import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Bookmark import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig @@ -23,7 +22,6 @@ import io.legado.app.lib.theme.ThemeStore import io.legado.app.service.help.Download import io.legado.app.service.help.ReadBook import io.legado.app.utils.applyTint -import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.requestInputMethod import kotlinx.android.synthetic.main.dialog_download_choice.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.* @@ -42,15 +40,14 @@ object Help { or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) - val hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) - if (hideNavigationBar) { + if (ReadBookConfig.hideNavigationBar) { flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION } if (toolBarHide) { - if (App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)) { + if (ReadBookConfig.hideStatusBar) { flag = flag or View.SYSTEM_UI_FLAG_FULLSCREEN } - if (hideNavigationBar) { + if (ReadBookConfig.hideNavigationBar) { flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 248f2b5e5..48bdd6cf6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Bundle import android.os.Handler import android.view.* +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.view.get import androidx.core.view.isVisible import androidx.core.view.size @@ -24,7 +25,7 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.theme.accentColor -import io.legado.app.receiver.TimeElectricityReceiver +import io.legado.app.receiver.TimeBatteryReceiver import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook @@ -32,10 +33,10 @@ import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.read.config.* import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR -import io.legado.app.ui.book.read.page.ChapterProvider +import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.PageView +import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.delegate.PageDelegate -import io.legado.app.ui.book.read.page.entities.SelectPoint import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.changesource.ChangeSourceDialog import io.legado.app.ui.chapterlist.ChapterListActivity @@ -55,27 +56,32 @@ import org.jetbrains.anko.toast class ReadBookActivity : VMBaseActivity(R.layout.activity_book_read), View.OnTouchListener, PageView.CallBack, + TextActionMenu.CallBack, + ContentTextView.CallBack, ReadMenu.CallBack, ReadAloudDialog.CallBack, ChangeSourceDialog.CallBack, ReadBook.CallBack, + TocRegexDialog.CallBack, ColorPickerDialogListener { private val requestCodeChapterList = 568 private val requestCodeEditSource = 111 private val requestCodeReplace = 312 private var menu: Menu? = null + private var textActionMenu: TextActionMenu? = null override val viewModel: ReadBookViewModel get() = getViewModel(ReadBookViewModel::class.java) - override val isInitFinish: Boolean - get() = viewModel.isInitFinish + override val isInitFinish: Boolean get() = viewModel.isInitFinish private val mHandler = Handler() private val keepScreenRunnable: Runnable = Runnable { Help.keepScreenOn(window, false) } private var screenTimeOut: Long = 0 - private var timeElectricityReceiver: TimeElectricityReceiver? = null + private var timeBatteryReceiver: TimeBatteryReceiver? = null + override val pageFactory: TextPageFactory get() = page_view.pageFactory + override val headerHeight: Int get() = page_view.curPage.headerHeight override fun onActivityCreated(savedInstanceState: Bundle?) { Help.upLayoutInDisplayCutoutMode(window) @@ -98,15 +104,15 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onResume() { super.onResume() upSystemUiVisibility() - timeElectricityReceiver = TimeElectricityReceiver.register(this) + timeBatteryReceiver = TimeBatteryReceiver.register(this) page_view.upTime() } override fun onPause() { super.onPause() - timeElectricityReceiver?.let { + timeBatteryReceiver?.let { unregisterReceiver(it) - timeElectricityReceiver = null + timeBatteryReceiver = null } upSystemUiVisibility() } @@ -209,6 +215,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo R.id.menu_book_info -> ReadBook.book?.let { startActivity(Pair("bookUrl", it.bookUrl)) } + R.id.menu_toc_regex -> TocRegexDialog.show( + supportFragmentManager, + ReadBook.book?.tocUrl + ) } return super.onCompatOptionsItemSelected(item) } @@ -318,14 +328,98 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo when (event.action) { MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> { when (v.id) { - R.id.cursor_left -> page_view.selectStartMove(event.x, event.y) - R.id.cursor_right -> page_view.selectEndMove(event.x, event.y) + R.id.cursor_left -> page_view.curPage.selectStartMove( + event.rawX + cursor_left.width, + event.rawY - cursor_left.height + ) + R.id.cursor_right -> page_view.curPage.selectEndMove( + event.rawX - cursor_right.width, + event.rawY - cursor_right.height + ) } } } return true } + /** + * 更新文字选择开始位置 + */ + override fun upSelectedStart(x: Float, y: Float) { + cursor_left.x = x - cursor_left.width + cursor_left.y = y + cursor_left.visible(true) + showTextActionMenu() + } + + /** + * 更新文字选择结束位置 + */ + override fun upSelectedEnd(x: Float, y: Float) { + cursor_right.x = x + cursor_right.y = y + cursor_right.visible(true) + showTextActionMenu() + } + + /** + * 取消文字选择 + */ + override fun onCancelSelect() { + cursor_left.invisible() + cursor_right.invisible() + textActionMenu?.dismiss() + } + + /** + * 显示文本操作菜单 + */ + private fun showTextActionMenu() { + textActionMenu ?: let { + textActionMenu = TextActionMenu(this, this) + } + val x = cursor_left.x.toInt() + cursor_left.width + val y = if (cursor_left.y - statusBarHeight > ReadBookConfig.textSize.dp * 1.5 + 20.dp) { + (page_view.height - cursor_left.y + ReadBookConfig.textSize.dp * 1.5).toInt() + } else { + (page_view.height - cursor_left.y - cursor_left.height - 40.dp).toInt() + } + textActionMenu?.let { popup -> + if (!popup.isShowing) { + popup.showAtLocation(cursor_left, Gravity.BOTTOM or Gravity.START, x, y) + } else { + popup.update(x, y, WRAP_CONTENT, WRAP_CONTENT) + } + } + } + + /** + * 当前选择的文本 + */ + override val selectedText: String get() = page_view.curPage.selectedText + + /** + * 文本选择菜单操作 + */ + override fun onMenuItemSelected(itemId: Int): Boolean { + when (itemId) { + R.id.menu_replace -> { + ReplaceEditDialog.show(supportFragmentManager, pattern = selectedText) + return true + } + } + return false + } + + /** + * 文本选择菜单操作完成 + */ + override fun onMenuActionFinally() { + textActionMenu?.dismiss() + page_view.curPage.cancelSelect() + page_view.pageDelegate?.isTextSelected = false + } + /** * 音量键翻页 */ @@ -357,12 +451,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } - override fun upContent(position: Int) { + /** + * 更新内容 + */ + override fun upContent(relativePosition: Int) { launch { - page_view.upContent(position) + page_view.upContent(relativePosition) } } + /** + * 更新视图 + */ override fun upView() { launch { ReadBook.curTextChapter?.let { @@ -380,12 +480,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } + /** + * 更新进度条 + */ override fun upPageProgress() { launch { seek_read_page.progress = ReadBook.durPageIndex } } + /** + * 显示菜单 + */ override fun showMenuBar() { read_menu.runMenuIn() } @@ -397,12 +503,6 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo viewModel.changeTo(book) } - override fun setPageIndex(pageIndex: Int) { - ReadBook.durPageIndex = pageIndex - ReadBook.saveRead() - ReadBook.curPageChanged() - } - override fun clickCenter() { if (BaseReadAloudService.isRun) { showReadAloudDialog() @@ -411,19 +511,16 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } - override fun selectText(selectPoint: SelectPoint) { - cursor_left.x = selectPoint.startX - cursor_left.width - cursor_left.y = selectPoint.startY - cursor_right.x = selectPoint.endX - cursor_right.y = selectPoint.endY - cursor_left.visible() - cursor_right.visible() - } - + /** + * 显示朗读菜单 + */ override fun showReadAloudDialog() { ReadAloudDialog().show(supportFragmentManager, "readAloud") } + /** + * 自动翻页 + */ override fun autoPage() { } @@ -495,6 +592,13 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo */ override fun onDialogDismissed(dialogId: Int) = Unit + override fun onTocRegexDialogResult(tocRegex: String) { + ReadBook.book?.let { + it.tocUrl = tocRegex + viewModel.loadChapterList(it) + } + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK) { @@ -528,6 +632,8 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onDestroy() { super.onDestroy() mHandler.removeCallbacks(keepScreenRunnable) + textActionMenu?.dismiss() + page_view.onDestroy() } override fun observeLiveBus() { @@ -549,7 +655,6 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo upSystemUiVisibility() page_view.upBg() page_view.upStyle() - ChapterProvider.upStyle(ReadBookConfig.durConfig) if (it) { ReadBook.loadContent() } else { @@ -586,7 +691,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo upScreenTimeOut() } observeEvent(PreferKey.textSelectAble) { - page_view.upSelectAble(it) + page_view.curPage.upSelectAble(it) } } @@ -605,7 +710,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo Help.keepScreenOn(window, true) return } - val t = screenTimeOut - getScreenOffTime() + val t = screenTimeOut - sysScreenOffTime if (t > 0) { mHandler.removeCallbacks(keepScreenRunnable) Help.keepScreenOn(window, true) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index db7529cab..2ebab7875 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -99,6 +99,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { execute { if (book.isLocalBook()) { AnalyzeTxtFile.analyze(context, book).let { + App.db.bookChapterDao().delByBook(book.bookUrl) App.db.bookChapterDao().insert(*it.toTypedArray()) App.db.bookDao().update(book) ReadBook.chapterSize = it.size diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt index 12bbb6d8b..6dec43d12 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt @@ -10,8 +10,8 @@ import android.widget.SeekBar import androidx.core.view.isVisible import io.legado.app.App import io.legado.app.R -import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig +import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.buttonDisabledColor import io.legado.app.service.help.ReadBook @@ -199,9 +199,8 @@ class ReadMenu : FrameLayout { vw_menu_bg.onClick { runMenuOut() } vwNavigationBar.layoutParams = vwNavigationBar.layoutParams.apply { height = - if (context.getPrefBoolean(PreferKey.hideNavigationBar) - && Help.isNavigationBarExist(activity) - ) context.getNavigationBarHeight() + if (ReadBookConfig.hideNavigationBar && Help.isNavigationBarExist(activity)) + context.navigationBarHeight else 0 } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt new file mode 100644 index 000000000..4dc9ba7da --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt @@ -0,0 +1,173 @@ +package io.legado.app.ui.book.read + +import android.annotation.SuppressLint +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.content.pm.ResolveInfo +import android.net.Uri +import android.os.Build +import android.view.LayoutInflater +import android.view.Menu +import android.view.ViewGroup +import android.widget.PopupWindow +import androidx.annotation.RequiresApi +import androidx.appcompat.view.SupportMenuInflater +import androidx.appcompat.view.menu.MenuBuilder +import androidx.appcompat.view.menu.MenuItemImpl +import androidx.appcompat.widget.PopupMenu +import androidx.core.view.size +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.legado.app.R +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.utils.isAbsUrl +import io.legado.app.utils.sendToClip +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.item_fillet_text.view.* +import kotlinx.android.synthetic.main.popup_action_menu.view.* +import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.share +import org.jetbrains.anko.toast + +@SuppressLint("RestrictedApi") +class TextActionMenu(private val context: Context, private val callBack: CallBack) : + PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) { + + init { + @SuppressLint("InflateParams") + contentView = LayoutInflater.from(context).inflate(R.layout.popup_action_menu, null) + + isTouchable = true + isOutsideTouchable = false + isFocusable = false + + initRecyclerView() + } + + private fun initRecyclerView() = with(contentView) { + val adapter = Adapter(context) + recycler_view.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) + recycler_view.adapter = adapter + val menu = MenuBuilder(context) + SupportMenuInflater(context).inflate(R.menu.content_select_action, menu) + adapter.setItems(menu.visibleItems) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val popupMenu = PopupMenu(context, iv_menu_more) + onInitializeMenu(popupMenu.menu) + if (popupMenu.menu.size > 0) { + iv_menu_more.visible() + popupMenu.setOnMenuItemClickListener { item -> + item.intent?.let { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + it.putExtra(Intent.EXTRA_PROCESS_TEXT, callBack.selectedText) + context.startActivity(it) + } + } + this@TextActionMenu.dismiss() + true + } + } + iv_menu_more.onClick { + popupMenu.show() + } + } + } + + inner class Adapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_text) { + + override fun convert( + holder: ItemViewHolder, + item: MenuItemImpl, + payloads: MutableList + ) { + with(holder.itemView) { + text_view.text = item.title + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.onClick { + getItem(holder.layoutPosition)?.let { + if (!callBack.onMenuItemSelected(it.itemId)) { + onMenuItemSelected(it) + } + } + } + } + } + + private fun onMenuItemSelected(item: MenuItemImpl) { + when (item.itemId) { + R.id.menu_copy -> context.sendToClip(callBack.selectedText) + R.id.menu_share_str -> context.share(callBack.selectedText) + R.id.menu_browser -> { + try { + val intent = if (callBack.selectedText.isAbsUrl()) { + Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(callBack.selectedText) + } + } else { + Intent(Intent.ACTION_WEB_SEARCH).apply { + putExtra(SearchManager.QUERY, callBack.selectedText) + } + } + context.startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + context.toast(e.localizedMessage ?: "ERROR") + } + } + } + callBack.onMenuActionFinally() + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun createProcessTextIntent(): Intent { + return Intent() + .setAction(Intent.ACTION_PROCESS_TEXT) + .setType("text/plain") + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun getSupportedActivities(): List { + return context.packageManager + .queryIntentActivities(createProcessTextIntent(), 0) + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun createProcessTextIntentForResolveInfo(info: ResolveInfo): Intent { + return createProcessTextIntent() + .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false) + .setClassName(info.activityInfo.packageName, info.activityInfo.name) + } + + /** + * Start with a menu Item order value that is high enough + * so that your "PROCESS_TEXT" menu items appear after the + * standard selection menu items like Cut, Copy, Paste. + */ + @RequiresApi(Build.VERSION_CODES.M) + private fun onInitializeMenu(menu: Menu) { + // Start with a menu Item order value that is high enough + // so that your "PROCESS_TEXT" menu items appear after the + // standard selection menu items like Cut, Copy, Paste. + var menuItemOrder = 100 + for (resolveInfo in getSupportedActivities()) { + menu.add( + Menu.NONE, Menu.NONE, + menuItemOrder++, resolveInfo.loadLabel(context.packageManager) + ).intent = createProcessTextIntentForResolveInfo(resolveInfo) + } + } + + interface CallBack { + val selectedText: String + + fun onMenuItemSelected(itemId: Int): Boolean + + fun onMenuActionFinally() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index f4f1d9ff8..5a3d3ec8b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -155,7 +155,7 @@ class BgTextConfigDialog : DialogFragment() { override fun registerListener(holder: ItemViewHolder) { holder.itemView.apply { this.onClick { - getItem(holder.layoutPosition)?.let { + getItemByLayoutPosition(holder.layoutPosition)?.let { ReadBookConfig.durConfig.setBg(1, it) ReadBookConfig.upBg() postEvent(EventBus.UP_CONFIG, false) diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index b087a1508..9b975e7e8 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt @@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat import io.legado.app.R import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey +import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.ATH import io.legado.app.ui.book.read.Help import io.legado.app.utils.getPrefBoolean @@ -92,8 +93,14 @@ class MoreConfigDialog : DialogFragment() { key: String? ) { when (key) { - PreferKey.hideStatusBar -> postEvent(EventBus.UP_CONFIG, true) - PreferKey.hideNavigationBar -> postEvent(EventBus.UP_CONFIG, true) + PreferKey.hideStatusBar -> { + ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) + postEvent(EventBus.UP_CONFIG, true) + } + PreferKey.hideNavigationBar -> { + ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar) + postEvent(EventBus.UP_CONFIG, true) + } PreferKey.keepLight -> postEvent(key, true) PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index 1cb0715e3..1155e1d7d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -10,7 +10,9 @@ import io.legado.app.R import io.legado.app.constant.EventBus import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.Help +import io.legado.app.utils.dp import io.legado.app.utils.postEvent +import io.legado.app.utils.visible import kotlinx.android.synthetic.main.dialog_read_padding.* class PaddingConfigDialog : DialogFragment() { @@ -49,7 +51,15 @@ class PaddingConfigDialog : DialogFragment() { ReadBookConfig.save() } - private fun initData() = with(ReadBookConfig.durConfig) { + private fun initData() = with(ReadBookConfig) { + if (hideStatusBar) { + tv_header_padding.visible() + dsb_header_padding_top.visible() + dsb_header_padding_bottom.visible() + dsb_header_padding_left.visible() + dsb_header_padding_right.visible() + tv_body_padding.setPadding(0, 10.dp, 0, 10.dp) + } //正文 dsb_padding_top.progress = paddingTop dsb_padding_bottom.progress = paddingBottom @@ -67,7 +77,7 @@ class PaddingConfigDialog : DialogFragment() { dsb_footer_padding_right.progress = footerPaddingRight } - private fun initView() = with(ReadBookConfig.durConfig) { + private fun initView() = with(ReadBookConfig) { //正文 dsb_padding_top.onChanged = { paddingTop = it diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index ecfdb4525..a6482caed 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -11,7 +11,7 @@ import androidx.fragment.app.DialogFragment import io.legado.app.R import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey -import io.legado.app.help.ImageLoader +import io.legado.app.help.BookHelp import io.legado.app.help.ReadBookConfig import io.legado.app.lib.dialogs.selector import io.legado.app.lib.theme.accentColor @@ -19,7 +19,10 @@ import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.widget.font.FontSelectDialog -import io.legado.app.utils.* +import io.legado.app.utils.getIndexById +import io.legado.app.utils.getPrefString +import io.legado.app.utils.postEvent +import io.legado.app.utils.putPrefString import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.dialog_read_book_style.* import org.jetbrains.anko.sdk27.listeners.onCheckedChange @@ -28,6 +31,8 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { + val callBack = activity as? ReadBookActivity + override fun onStart() { super.onStart() val dm = DisplayMetrics() @@ -76,7 +81,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } private fun initData() { - requireContext().getPrefInt(PreferKey.pageAnim).let { + cb_share_layout.isChecked = ReadBookConfig.shareLayout + ReadBookConfig.pageAnim.let { if (it >= 0 && it < rg_page_anim.childCount) { rg_page_anim.check(rg_page_anim[it].id) } @@ -90,8 +96,15 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { chinese_converter.onChanged { postEvent(EventBus.UP_CONFIG, true) } + tv_title_center.onClick { + ReadBookConfig.apply { + titleCenter = !titleCenter + tv_title_center.isSelected = titleCenter + } + postEvent(EventBus.UP_CONFIG, true) + } tv_text_bold.onClick { - ReadBookConfig.durConfig.apply { + ReadBookConfig.apply { textBold = !textBold tv_text_bold.isSelected = textBold } @@ -105,43 +118,41 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { title = getString(R.string.text_indent), items = resources.getStringArray(R.array.indent).toList() ) { _, index -> - putPrefInt(PreferKey.bodyIndent, index) + BookHelp.bodyIndentCount = index postEvent(EventBus.UP_CONFIG, true) } } tv_padding.onClick { - val activity = activity dismiss() - if (activity is ReadBookActivity) { - activity.showPaddingConfig() - } + callBack?.showPaddingConfig() } dsb_text_size.onChanged = { - ReadBookConfig.durConfig.textSize = it + 5 + ReadBookConfig.textSize = it + 5 postEvent(EventBus.UP_CONFIG, true) } dsb_text_letter_spacing.onChanged = { - ReadBookConfig.durConfig.letterSpacing = (it - 50) / 100f + ReadBookConfig.letterSpacing = (it - 50) / 100f postEvent(EventBus.UP_CONFIG, true) } dsb_line_size.onChanged = { - ReadBookConfig.durConfig.lineSpacingExtra = it + ReadBookConfig.lineSpacingExtra = it postEvent(EventBus.UP_CONFIG, true) } dsb_paragraph_spacing.onChanged = { - ReadBookConfig.durConfig.paragraphSpacing = it + ReadBookConfig.paragraphSpacing = it postEvent(EventBus.UP_CONFIG, true) } rg_page_anim.onCheckedChange { _, checkedId -> - for (i in 0 until rg_page_anim.childCount) { - if (checkedId == rg_page_anim[i].id) { - requireContext().putPrefInt(PreferKey.pageAnim, i) - val activity = activity - if (activity is ReadBookActivity) { - activity.page_view.upPageAnim(i) - } - break - } + rg_page_anim.getIndexById(checkedId).let { + ReadBookConfig.pageAnim = it + callBack?.page_view?.upPageAnim() + } + } + cb_share_layout.onCheckedChangeListener = { checkBox, isChecked -> + if (checkBox.isPressed) { + ReadBookConfig.shareLayout = isChecked + upStyle() + postEvent(EventBus.UP_CONFIG, true) } } bg0.onClick { changeBg(0) } @@ -169,15 +180,13 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { private fun showBgTextConfig(index: Int): Boolean { dismiss() changeBg(index) - val activity = activity - if (activity is ReadBookActivity) { - activity.showBgTextConfig() - } + callBack?.showBgTextConfig() return true } private fun upStyle() { - ReadBookConfig.durConfig.let { + ReadBookConfig.let { + tv_title_center.isSelected = it.titleCenter tv_text_bold.isSelected = it.textBold dsb_text_size.progress = it.textSize - 5 dsb_text_letter_spacing.progress = (it.letterSpacing * 100).toInt() + 50 @@ -200,12 +209,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { 4 -> bg4 else -> bg0 } - ReadBookConfig.getConfig(i).apply { - when (bgType()) { - 2 -> ImageLoader.load(requireContext(), bgStr()).centerCrop().into(iv) - else -> iv.setImageDrawable(bgDrawable(100, 150)) - } - } + iv.setImageDrawable(ReadBookConfig.getConfig(i).bgDrawable(100, 150)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt new file mode 100644 index 000000000..f2046ac4b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt @@ -0,0 +1,259 @@ +package io.legado.app.ui.book.read.config + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.constant.Theme +import io.legado.app.data.entities.TxtTocRule +import io.legado.app.help.ItemTouchCallback +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.cancelButton +import io.legado.app.lib.dialogs.customView +import io.legado.app.lib.dialogs.okButton +import io.legado.app.model.localBook.AnalyzeTxtFile +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.applyTint +import kotlinx.android.synthetic.main.dialog_toc_regex.* +import kotlinx.android.synthetic.main.dialog_toc_regex_edit.view.* +import kotlinx.android.synthetic.main.item_toc_regex.view.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import org.jetbrains.anko.sdk27.listeners.onClick +import java.util.* + + +class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { + + private lateinit var adapter: TocRegexAdapter + private var tocRegexLiveData: LiveData>? = null + var selectedName: String? = null + private var durRegex: String? = null + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.8).toInt()) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_toc_regex, container) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + durRegex = arguments?.getString("tocRegex") + tool_bar.setTitle(R.string.txt_toc_regex) + tool_bar.inflateMenu(R.menu.txt_toc_regex) + tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.setOnMenuItemClickListener(this) + initView() + initData() + } + + private fun initView() { + adapter = TocRegexAdapter(requireContext()) + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) + recycler_view.adapter = adapter + val itemTouchCallback = ItemTouchCallback() + itemTouchCallback.onItemTouchCallbackListener = adapter + itemTouchCallback.isCanDrag = true + ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) + tv_cancel.onClick { + dismiss() + } + tv_ok.onClick { + adapter.getItems().forEach { tocRule -> + if (selectedName == tocRule.name) { + val callBack = activity as? CallBack + callBack?.onTocRegexDialogResult(tocRule.rule) + dismiss() + return@onClick + } + } + } + } + + private fun initData() { + tocRegexLiveData?.removeObservers(viewLifecycleOwner) + tocRegexLiveData = App.db.txtTocRule().observeAll() + tocRegexLiveData?.observe(viewLifecycleOwner, Observer { tocRules -> + initSelectedName(tocRules) + adapter.setItems(tocRules) + }) + } + + private fun initSelectedName(tocRules: List) { + if (selectedName == null && durRegex != null) { + tocRules.forEach { + if (durRegex == it.rule) { + selectedName = it.name + return@forEach + } + } + if (selectedName == null) { + selectedName = "" + } + } + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + when (item?.itemId) { + R.id.menu_add -> editRule() + R.id.menu_default -> importDefault() + } + return false + } + + private fun importDefault() { + launch(IO) { + AnalyzeTxtFile.getDefaultRules().let { + App.db.txtTocRule().insert(*it.toTypedArray()) + } + } + } + + @SuppressLint("InflateParams") + private fun editRule(rule: TxtTocRule? = null) { + val tocRule = rule?.copy() ?: TxtTocRule() + requireContext().alert(titleResource = R.string.txt_toc_regex) { + var rootView: View? = null + customView { + LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_toc_regex_edit, null).apply { + rootView = this + tv_rule_name.setText(tocRule.name) + tv_rule_regex.setText(tocRule.rule) + } + } + okButton { + rootView?.apply { + tocRule.name = tv_rule_name.text.toString() + tocRule.rule = tv_rule_regex.text.toString() + saveRule(tocRule, rule) + } + } + cancelButton() + }.show().applyTint() + } + + private fun saveRule(rule: TxtTocRule, oldRule: TxtTocRule? = null) { + launch(IO) { + if (rule.serialNumber < 0) { + rule.serialNumber = adapter.getItems().lastOrNull()?.serialNumber ?: 0 + 1 + } + oldRule?.let { + App.db.txtTocRule().delete(oldRule) + } + App.db.txtTocRule().insert(rule) + } + } + + inner class TocRegexAdapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_toc_regex), + ItemTouchCallback.OnItemTouchCallbackListener { + + override fun convert(holder: ItemViewHolder, item: TxtTocRule, payloads: MutableList) { + holder.itemView.apply { + if (payloads.isEmpty()) { + rb_regex_name.text = item.name + rb_regex_name.isChecked = item.name == selectedName + swt_enabled.isChecked = item.enable + } else { + rb_regex_name.isChecked = item.name == selectedName + } + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + rb_regex_name.setOnCheckedChangeListener { buttonView, isChecked -> + if (buttonView.isPressed && isChecked) { + selectedName = getItem(holder.layoutPosition)?.name + updateItems(0, itemCount - 1, true) + } + } + swt_enabled.setOnCheckedChangeListener { buttonView, isChecked -> + if (buttonView.isPressed) { + getItem(holder.layoutPosition)?.let { + it.enable = isChecked + launch(IO) { + App.db.txtTocRule().update(it) + } + } + } + } + iv_edit.onClick { + editRule(getItem(holder.layoutPosition)) + } + iv_delete.onClick { + getItem(holder.layoutPosition)?.let { item -> + launch(IO) { + App.db.txtTocRule().delete(item) + } + } + } + } + } + + private var isMoved = false + + override fun onMove(srcPosition: Int, targetPosition: Int): Boolean { + Collections.swap(getItems(), srcPosition, targetPosition) + notifyItemMoved(srcPosition, targetPosition) + isMoved = true + return super.onMove(srcPosition, targetPosition) + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + if (isMoved) { + for ((index, item) in getItems().withIndex()) { + item.serialNumber = index + 1 + } + launch(IO) { + App.db.txtTocRule().update(*getItems().toTypedArray()) + } + } + isMoved = false + } + } + + + companion object { + fun show(fragmentManager: FragmentManager, tocRegex: String? = null) { + val dialog = TocRegexDialog() + val bundle = Bundle() + bundle.putString("tocRegex", tocRegex) + dialog.arguments = bundle + dialog.show(fragmentManager, "tocRegexDialog") + } + } + + interface CallBack { + fun onTocRegexDialogResult(tocRegex: String) {} + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt index 8d5cdc422..2c3996fd8 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt @@ -8,9 +8,12 @@ import android.text.TextUtils import io.legado.app.App import io.legado.app.constant.PreferKey import io.legado.app.data.entities.BookChapter +import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.ReadBookConfig -import io.legado.app.ui.book.read.page.entities.* +import io.legado.app.ui.book.read.page.entities.TextChapter +import io.legado.app.ui.book.read.page.entities.TextLine +import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.utils.dp import io.legado.app.utils.getPrefString import io.legado.app.utils.removePref @@ -20,10 +23,12 @@ import io.legado.app.utils.removePref object ChapterProvider { var viewWidth = 0 var viewHeight = 0 - private var visibleWidth = 0 - private var visibleHeight = 0 - private var paddingLeft = 0 - private var paddingTop = 0 + var paddingLeft = 0 + var paddingTop = 0 + var visibleWidth = 0 + var visibleHeight = 0 + var visibleRight = 0 + var visibleBottom = 0 private var lineSpacingExtra = 0f private var paragraphSpacing = 0 var typeface: Typeface = Typeface.SANS_SERIF @@ -32,50 +37,12 @@ object ChapterProvider { private var bodyIndent = BookHelp.bodyIndent init { - upStyle(ReadBookConfig.durConfig) - } - - fun upStyle(config: ReadBookConfig.Config) { - typeface = try { - val fontPath = App.INSTANCE.getPrefString(PreferKey.readBookFont) - if (!TextUtils.isEmpty(fontPath)) { - Typeface.createFromFile(fontPath) - } else { - Typeface.SANS_SERIF - } - } catch (e: Exception) { - App.INSTANCE.removePref(PreferKey.readBookFont) - Typeface.SANS_SERIF - } - //标题 - titlePaint.isAntiAlias = true - titlePaint.color = config.textColor() - titlePaint.letterSpacing = config.letterSpacing - titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD) - //正文 - contentPaint.isAntiAlias = true - contentPaint.color = config.textColor() - contentPaint.letterSpacing = config.letterSpacing - val bold = if (config.textBold) Typeface.BOLD else Typeface.NORMAL - contentPaint.typeface = Typeface.create(typeface, bold) - //间距 - lineSpacingExtra = config.lineSpacingExtra.dp.toFloat() - paragraphSpacing = config.paragraphSpacing.dp - titlePaint.textSize = (config.textSize + 2).dp.toFloat() - contentPaint.textSize = config.textSize.dp.toFloat() - - bodyIndent = BookHelp.bodyIndent - - upSize(config) - } - - fun upSize(config: ReadBookConfig.Config) { - paddingLeft = config.paddingLeft.dp - paddingTop = config.paddingTop.dp - visibleWidth = viewWidth - paddingLeft - config.paddingRight.dp - visibleHeight = viewHeight - paddingTop - config.paddingBottom.dp + upStyle() } + /** + * 获取拆分完的章节数据 + */ fun getTextChapter( bookChapter: BookChapter, content: String, @@ -111,6 +78,8 @@ object ChapterProvider { durY = joinBody(text, durY, textPages, pageLines, pageLengths, stringBuilder) } } + textPages.last().height = durY + 20.dp + textPages.last().text = stringBuilder.toString() if (pageLines.size < textPages.size) { pageLines.add(textPages.last().textLines.size) } @@ -152,61 +121,39 @@ object ChapterProvider { Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true ) for (lineIndex in 0 until layout.lineCount) { + textPages.last().height = durY durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) val textLine = TextLine(isTitle = true) if (durY < visibleHeight) { textPages.last().textLines.add(textLine) } else { - durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) textPages.last().text = stringBuilder.toString() stringBuilder.clear() pageLines.add(textPages.last().textLines.size) pageLengths.add(textPages.last().text.length) + //新页面 + durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) textPages.add(TextPage()) textPages.last().textLines.add(textLine) } - textLine.lineBottom = paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex)) - textLine.lineTop = paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)) + textLine.lineTop = (paddingTop + durY - + (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() + textLine.lineBase = (paddingTop + durY - + (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat() + textLine.lineBottom = textLine.lineBase + titlePaint.fontMetrics.descent val words = title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) stringBuilder.append(words) textLine.text = words - val desiredWidth = layout.getLineMax(lineIndex) + val desiredWidth = layout.getLineWidth(lineIndex) if (lineIndex != layout.lineCount - 1) { - val gapCount: Int = words.length - 1 - val d = (visibleWidth - desiredWidth) / gapCount - var x = 0f - for (i in words.indices) { - val char = words[i].toString() - val cw = StaticLayout.getDesiredWidth(char, titlePaint) - val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) - val textChar = TextChar( - charData = char, - leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), - rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) - ) - textLine.textChars.add(textChar) - x = x1 - } + addCharsToLineMiddle(textLine, words, titlePaint, desiredWidth, 0f) } else { //最后一行 - textLine.text = "$words\n" - stringBuilder.append("\n") - var x = 0f - for (i in words.indices) { - val char = words[i].toString() - val cw = StaticLayout.getDesiredWidth(char, titlePaint) - val x1 = x + cw - val textChar = TextChar( - charData = char, - leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), - rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) - ) - textLine.textChars.add(textChar) - x = x1 - } + val x = if (ReadBookConfig.titleCenter) + (visibleWidth - layout.getLineWidth(lineIndex)) / 2 + else 0f + addCharsToLineLast(textLine, words, stringBuilder, titlePaint, x) } } durY += paragraphSpacing @@ -230,92 +177,192 @@ object ChapterProvider { Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true ) for (lineIndex in 0 until layout.lineCount) { - val textLine = TextLine(isTitle = false) + textPages.last().height = durY durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) + val textLine = TextLine() if (durY < visibleHeight) { textPages.last().textLines.add(textLine) } else { - durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) textPages.last().text = stringBuilder.toString() stringBuilder.clear() pageLines.add(textPages.last().textLines.size) pageLengths.add(textPages.last().text.length) + //新页面 + durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) textPages.add(TextPage()) textPages.last().textLines.add(textLine) } - textLine.lineBottom = paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex)) - textLine.lineTop = paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)) - var words = + textLine.lineTop = (paddingTop + durY - + (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() + textLine.lineBase = (paddingTop + durY - + (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat() + textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent + val words = text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) stringBuilder.append(words) textLine.text = words - val desiredWidth = layout.getLineMax(lineIndex) + val desiredWidth = layout.getLineWidth(lineIndex) if (lineIndex == 0 && layout.lineCount > 1) { //第一行 - var x = 0f - val icw = StaticLayout.getDesiredWidth(bodyIndent, contentPaint) - var x1 = x + icw - val textChar = TextChar( - charData = bodyIndent, - leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), - rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) - ) - textLine.textChars.add(textChar) - x = x1 - words = words.replaceFirst(bodyIndent, "") - val gapCount: Int = words.length - 1 - val d = (visibleWidth - desiredWidth) / gapCount - for (i in words.indices) { - val char = words[i].toString() - val cw = StaticLayout.getDesiredWidth(char, contentPaint) - x1 = if (i != words.lastIndex) x + cw + d else x + cw - val textChar1 = TextChar( - charData = char, - leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), - rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) - ) - textLine.textChars.add(textChar1) - x = x1 - } + addCharsToLineFirst(textLine, words, contentPaint, desiredWidth) } else if (lineIndex == layout.lineCount - 1) { //最后一行 - stringBuilder.append("\n") - textLine.text = "$words\n" - var x = 0f - for (i in words.indices) { - val char = words[i].toString() - val cw = StaticLayout.getDesiredWidth(char, contentPaint) - val x1 = x + cw - val textChar = TextChar( - charData = char, - leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), - rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) - ) - textLine.textChars.add(textChar) - x = x1 - } + addCharsToLineLast(textLine, words, stringBuilder, contentPaint, 0f) } else { //中间行 - val gapCount: Int = words.length - 1 - val d = (visibleWidth - desiredWidth) / gapCount - var x = 0f - for (i in words.indices) { - val char = words[i].toString() - val cw = StaticLayout.getDesiredWidth(char, contentPaint) - val x1 = if (i != words.lastIndex) x + cw + d else x + cw - val textChar = TextChar( - charData = char, - leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), - rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) - ) - textLine.textChars.add(textChar) - x = x1 - } + addCharsToLineMiddle(textLine, words, contentPaint, desiredWidth, 0f) } } durY += paragraphSpacing return durY } + + /** + * 有缩进,两端对齐 + */ + private fun addCharsToLineFirst( + textLine: TextLine, + words: String, + textPaint: TextPaint, + desiredWidth: Float + ) { + var x = 0f + val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length + for (i in 0..bodyIndent.lastIndex) { + val x1 = x + icw + textLine.addTextChar( + charData = bodyIndent[i].toString(), + start = paddingLeft + x, + end = paddingLeft + x1 + ) + x = x1 + } + val words1 = words.replaceFirst(bodyIndent, "") + addCharsToLineMiddle(textLine, words1, textPaint, desiredWidth, x) + } + + /** + * 无缩进,两端对齐 + */ + private fun addCharsToLineMiddle( + textLine: TextLine, + words: String, + textPaint: TextPaint, + desiredWidth: Float, + startX: Float + ) { + val gapCount: Int = words.length - 1 + val d = (visibleWidth - desiredWidth) / gapCount + var x = startX + for (i in words.indices) { + val char = words[i] + val cw = StaticLayout.getDesiredWidth(char.toString(), textPaint) + val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) + textLine.addTextChar( + charData = char.toString(), + start = paddingLeft + x, + end = paddingLeft + x1 + ) + x = x1 + } + exceed(textLine, words) + } + + /** + * 最后一行,自然排列 + */ + private fun addCharsToLineLast( + textLine: TextLine, + words: String, + stringBuilder: StringBuilder, + textPaint: TextPaint, + startX: Float + ) { + stringBuilder.append("\n") + textLine.text = "$words\n" + var x = startX + for (i in words.indices) { + val char = words[i].toString() + val cw = StaticLayout.getDesiredWidth(char, textPaint) + val x1 = x + cw + textLine.addTextChar( + charData = char, + start = paddingLeft + x, + end = paddingLeft + x1 + ) + x = x1 + } + exceed(textLine, words) + } + + /** + * 超出边界处理 + */ + private fun exceed(textLine: TextLine, words: String) { + val endX = textLine.textChars.last().end + if (endX > visibleRight) { + val cc = (endX - visibleRight) / words.length + for (i in 0..words.lastIndex) { + textLine.getTextCharReverseAt(i).let { + val py = cc * (words.length - i) + it.start = it.start - py + it.end = it.end - py + } + } + } + } + + /** + * 更新样式 + */ + fun upStyle() { + typeface = try { + val fontPath = App.INSTANCE.getPrefString(PreferKey.readBookFont) + if (!TextUtils.isEmpty(fontPath)) { + Typeface.createFromFile(fontPath) + } else { + when (AppConfig.systemTypefaces) { + 1 -> Typeface.SERIF + 2 -> Typeface.MONOSPACE + else -> Typeface.SANS_SERIF + } + } + } catch (e: Exception) { + App.INSTANCE.removePref(PreferKey.readBookFont) + Typeface.SANS_SERIF + } + //标题 + titlePaint.isAntiAlias = true + titlePaint.color = ReadBookConfig.durConfig.textColor() + titlePaint.letterSpacing = ReadBookConfig.letterSpacing + titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD) + //正文 + contentPaint.isAntiAlias = true + contentPaint.color = ReadBookConfig.durConfig.textColor() + contentPaint.letterSpacing = ReadBookConfig.letterSpacing + val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL + contentPaint.typeface = Typeface.create(typeface, bold) + //间距 + lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp.toFloat() + paragraphSpacing = ReadBookConfig.paragraphSpacing.dp + titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat() + contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat() + + bodyIndent = BookHelp.bodyIndent + + upSize() + } + + /** + * 更新View尺寸 + */ + fun upSize() { + paddingLeft = ReadBookConfig.paddingLeft.dp + paddingTop = ReadBookConfig.paddingTop.dp + visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp + visibleHeight = viewHeight - paddingTop - ReadBookConfig.paddingBottom.dp + visibleRight = paddingLeft + visibleWidth + visibleBottom = paddingTop + visibleHeight + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt deleted file mode 100644 index f274b2744..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.legado.app.ui.book.read.page - -import android.view.ActionMode -import android.view.Menu -import android.view.MenuItem -import android.widget.TextView - -import io.legado.app.R -import io.legado.app.constant.EventBus -import io.legado.app.utils.postEvent - -class ContentSelectActionCallback(private val textView: TextView) : ActionMode.Callback { - - override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { - when (item?.itemId) { - R.id.menu_replace -> { - val text = textView.text.substring(textView.selectionStart, textView.selectionEnd) - postEvent(EventBus.REPLACE, text) - mode?.finish() - return true - } - } - return false - } - - override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { - mode?.menuInflater?.inflate(R.menu.content_select_action, menu) - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { - return false - } - - override fun onDestroyActionMode(mode: ActionMode?) { - - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 52176ac4d..d17e55a67 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -1,124 +1,591 @@ package io.legado.app.ui.book.read.page -import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.graphics.Paint +import android.graphics.RectF import android.util.AttributeSet -import android.view.MotionEvent import android.view.View import io.legado.app.R import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.accentColor -import io.legado.app.ui.book.read.page.entities.SelectPoint +import io.legado.app.ui.book.read.page.entities.TextChar import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.utils.activity import io.legado.app.utils.getCompatColor import io.legado.app.utils.getPrefBoolean class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { + var selectAble = context.getPrefBoolean(PreferKey.textSelectAble) + var upView: ((TextPage) -> Unit)? = null private val selectedPaint by lazy { Paint().apply { color = context.getCompatColor(R.color.btn_bg_press_2) style = Paint.Style.FILL } } - var selectAble = context.getPrefBoolean(PreferKey.textSelectAble) - var selectStartLine = 0 - var selectStartChar = 0 - var selectEndLine = 0 - var selectEndChar = 0 - private var textPage: TextPage? = null + private var callBack: CallBack + private val visibleRect = RectF() + private val selectStart = arrayOf(0, 0, 0) + private val selectEnd = arrayOf(0, 0, 0) + private var textPage: TextPage = TextPage() + //滚动参数 + private val pageFactory: TextPageFactory get() = callBack.pageFactory + private val maxScrollOffset = 100f + private var pageOffset = 0f + + init { + callBack = activity as CallBack + contentDescription = textPage.text + } - fun setContent(textPage: TextPage?) { + fun setContent(textPage: TextPage) { this.textPage = textPage + contentDescription = textPage.text invalidate() } + fun upVisibleRect() { + visibleRect.set( + ChapterProvider.paddingLeft.toFloat(), + ChapterProvider.paddingTop.toFloat(), + ChapterProvider.visibleRight.toFloat(), + ChapterProvider.visibleBottom.toFloat() + ) + } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - ReadBookConfig.durConfig.let { - ChapterProvider.viewWidth = w - ChapterProvider.viewHeight = h - ChapterProvider.upSize(ReadBookConfig.durConfig) + ChapterProvider.viewWidth = w + ChapterProvider.viewHeight = h + ChapterProvider.upSize() + upVisibleRect() + textPage.format() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.clipRect(visibleRect) + drawPage(canvas) + } + + /** + * 绘制页面 + */ + private fun drawPage(canvas: Canvas) { + var relativeOffset = relativeOffset(0) + textPage.textLines.forEach { textLine -> + val lineTop = textLine.lineTop + relativeOffset + val lineBase = textLine.lineBase + relativeOffset + val lineBottom = textLine.lineBottom + relativeOffset + drawChars( + canvas, + textLine.textChars, + lineTop, + lineBase, + lineBottom, + textLine.isTitle, + textLine.isReadAloud + ) + } + if (!ReadBookConfig.isScroll) return + //滚动翻页 + val nextPage = relativePage(1) + relativeOffset = relativeOffset(1) + nextPage.textLines.forEach { textLine -> + val lineTop = textLine.lineTop + relativeOffset + val lineBase = textLine.lineBase + relativeOffset + val lineBottom = textLine.lineBottom + relativeOffset + drawChars( + canvas, + textLine.textChars, + lineTop, + lineBase, + lineBottom, + textLine.isTitle, + textLine.isReadAloud + ) + } + relativeOffset = relativeOffset(2) + if (relativeOffset < ChapterProvider.visibleHeight) { + relativePage(2).textLines.forEach { textLine -> + val lineTop = textLine.lineTop + relativeOffset + val lineBase = textLine.lineBase + relativeOffset + val lineBottom = textLine.lineBottom + relativeOffset + drawChars( + canvas, + textLine.textChars, + lineTop, + lineBase, + lineBottom, + textLine.isTitle, + textLine.isReadAloud + ) + } } } - @SuppressLint("ClickableViewAccessibility") - override fun onTouchEvent(event: MotionEvent?): Boolean { + /** + * 绘制文字 + */ + private fun drawChars( + canvas: Canvas, + textChars: List, + lineTop: Float, + lineBase: Float, + lineBottom: Float, + isTitle: Boolean, + isReadAloud: Boolean + ) { + val textPaint = if (isTitle) ChapterProvider.titlePaint else ChapterProvider.contentPaint + textPaint.color = + if (isReadAloud) context.accentColor else ReadBookConfig.durConfig.textColor() + textChars.forEach { + canvas.drawText(it.charData, it.start, lineBase, textPaint) + if (it.selected) { + canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) + } + } + } + + /** + * 滚动事件 + */ + fun onScroll(mOffset: Float) { + if (mOffset == 0f) return + var offset = mOffset + if (offset > maxScrollOffset) { + offset = maxScrollOffset + } else if (offset < -maxScrollOffset) { + offset = -maxScrollOffset + } + + pageOffset += offset + if (pageOffset > 0) { + if (!pageFactory.hasPrev()) { + pageOffset = 0f + } else { + pageFactory.moveToPrev() + textPage = pageFactory.currentPage + pageOffset -= textPage.height + upView?.invoke(textPage) + } + } else if (pageOffset < -textPage.height) { + if (!pageFactory.hasNext()) { + pageOffset = -textPage.height.toFloat() + } else { + pageOffset += textPage.height + pageFactory.moveToNext() + textPage = pageFactory.currentPage + upView?.invoke(textPage) + } + } + invalidate() + } - return true + fun resetPageOffset() { + pageOffset = 0f } - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - textPage?.let { textPage -> - textPage.textLines.forEach { textLine -> - val textPaint = if (textLine.isTitle) { - ChapterProvider.titlePaint - } else { - ChapterProvider.contentPaint + /** + * 选择初始文字 + */ + fun selectText( + x: Float, + y: Float, + select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit + ) { + if (!selectAble) return + if (!visibleRect.contains(x, y)) return + var relativeOffset = relativeOffset(0) + for ((lineIndex, textLine) in textPage.textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + textChar.selected = true + invalidate() + selectStart[0] = 0 + selectStart[1] = lineIndex + selectStart[2] = charIndex + selectEnd[0] = 0 + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + select(0, lineIndex, charIndex) + return + } } - textPaint.color = if (textLine.isReadAloud) { - context.accentColor - } else { - ReadBookConfig.durConfig.textColor() + return + } + } + if (!ReadBookConfig.isScroll) return + //滚动翻页 + relativeOffset = relativeOffset(1) + if (relativeOffset >= ChapterProvider.visibleHeight) return + val nextPage = relativePage(1) + for ((lineIndex, textLine) in nextPage.textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + textChar.selected = true + invalidate() + selectStart[0] = 1 + selectStart[1] = lineIndex + selectStart[2] = charIndex + selectEnd[0] = 1 + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + select(1, lineIndex, charIndex) + return + } } - textLine.textChars.forEach { - canvas.drawText( - it.charData, - it.leftBottomPosition.x, - it.leftBottomPosition.y.toFloat(), - textPaint - ) - if (it.selected) { - canvas.drawRect( - it.leftBottomPosition.x, - it.rightTopPosition.y.toFloat(), - it.rightTopPosition.x, - it.leftBottomPosition.y.toFloat(), - selectedPaint - ) + return + } + } + relativeOffset = relativeOffset(2) + if (relativeOffset >= ChapterProvider.visibleHeight) return + for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + textChar.selected = true + invalidate() + selectStart[0] = 2 + selectStart[1] = lineIndex + selectStart[2] = charIndex + selectEnd[0] = 2 + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + select(2, lineIndex, charIndex) + return } } + return } } } - fun selectText(x: Float, y: Float): SelectPoint? { - textPage?.let { textPage -> - for ((lineIndex, textLine) in textPage.textLines.withIndex()) { - if (y > textLine.lineTop && y < textLine.lineBottom) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { - textChar.selected = true - invalidate() - selectStartLine = lineIndex - selectStartChar = charIndex - selectEndLine = lineIndex - selectEndChar = charIndex - return SelectPoint( - textChar.leftBottomPosition.x, - textChar.leftBottomPosition.y.toFloat(), - textChar.rightTopPosition.x, - textChar.leftBottomPosition.y.toFloat() - ) + /** + * 开始选择符移动 + */ + fun selectStartMove(x: Float, y: Float) { + if (!visibleRect.contains(x, y)) return + var relativeOffset = relativeOffset(0) + for ((lineIndex, textLine) in textPage.textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectStart[0] != 0 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { + if (selectToInt(0, lineIndex, charIndex) > selectToInt(selectEnd)) { + return + } + selectStart[0] = 0 + selectStart[1] = lineIndex + selectStart[2] = charIndex + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) + upSelectChars() + } + return + } + } + return + } + } + if (!ReadBookConfig.isScroll) return + //滚动翻页 + relativeOffset = relativeOffset(1) + if (relativeOffset >= ChapterProvider.visibleHeight) return + for ((lineIndex, textLine) in relativePage(1).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectStart[0] != 1 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { + if (selectToInt(1, lineIndex, charIndex) > selectToInt(selectEnd)) { + return + } + selectStart[0] = 1 + selectStart[1] = lineIndex + selectStart[2] = charIndex + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) + upSelectChars() } + return } - break } + return + } + } + relativeOffset = relativeOffset(2) + if (relativeOffset >= ChapterProvider.visibleHeight) return + for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectStart[0] != 2 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { + if (selectToInt(2, lineIndex, charIndex) > selectToInt(selectEnd)) { + return + } + selectStart[0] = 2 + selectStart[1] = lineIndex + selectStart[2] = charIndex + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) + upSelectChars() + } + return + } + } + return } } - return null } - fun selectStartMove(x: Float, y: Float) { + /** + * 结束选择符移动 + */ + fun selectEndMove(x: Float, y: Float) { + if (!visibleRect.contains(x, y)) return + var relativeOffset = relativeOffset(0) + for ((lineIndex, textLine) in textPage.textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectEnd[0] != 0 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { + if (selectToInt(0, lineIndex, charIndex) < selectToInt(selectStart)) { + return + } + selectEnd[0] = 0 + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + upSelectChars() + } + return + } + } + return + } + } + if (!ReadBookConfig.isScroll) return + //滚动翻页 + relativeOffset = relativeOffset(1) + if (relativeOffset >= ChapterProvider.visibleHeight) return + for ((lineIndex, textLine) in relativePage(1).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectEnd[0] != 1 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { + if (selectToInt(1, lineIndex, charIndex) < selectToInt(selectStart)) { + return + } + selectEnd[0] = 1 + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + upSelectChars() + } + return + } + } + return + } + } + relativeOffset = relativeOffset(2) + if (relativeOffset >= ChapterProvider.visibleHeight) return + for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectEnd[0] != 2 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { + if (selectToInt(2, lineIndex, charIndex) < selectToInt(selectStart)) { + return + } + selectEnd[0] = 2 + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + upSelectChars() + } + return + } + } + return + } + } + } + /** + * 选择开始文字 + */ + fun selectStartMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) { + selectStart[0] = relativePage + selectStart[1] = lineIndex + selectStart[2] = charIndex + val textLine = relativePage(relativePage).textLines[lineIndex] + val textChar = textLine.textChars[charIndex] + upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset(relativePage)) + upSelectChars() } - fun selectEndMove(x: Float, y: Float) { + /** + * 选择结束文字 + */ + fun selectEndMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) { + selectEnd[0] = relativePage + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + val textLine = relativePage(relativePage).textLines[lineIndex] + val textChar = textLine.textChars[charIndex] + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset(relativePage)) + upSelectChars() + } + + private fun upSelectChars() { + val last = if (ReadBookConfig.isScroll) 2 else 0 + for (relativePos in 0..last) { + for ((lineIndex, textLine) in relativePage(relativePos).textLines.withIndex()) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + textChar.selected = + if (relativePos == selectStart[0] + && relativePos == selectEnd[0] + && lineIndex == selectStart[1] + && lineIndex == selectEnd[1] + ) { + charIndex in selectStart[2]..selectEnd[2] + } else if (relativePos == selectStart[0] && lineIndex == selectStart[1]) { + charIndex >= selectStart[2] + } else if (relativePos == selectEnd[0] && lineIndex == selectEnd[1]) { + charIndex <= selectEnd[2] + } else if (relativePos == selectStart[0] && relativePos == selectEnd[0]) { + lineIndex in (selectStart[1] + 1) until selectEnd[1] + } else if (relativePos == selectStart[0]) { + lineIndex > selectStart[1] + } else if (relativePos == selectEnd[0]) { + lineIndex < selectEnd[1] + } else { + relativePos in selectStart[0] + 1 until selectEnd[0] + } + } + } + } + invalidate() + } + + private fun upSelectedStart(x: Float, y: Float) { + callBack.upSelectedStart(x, y + callBack.headerHeight) + } + private fun upSelectedEnd(x: Float, y: Float) { + callBack.upSelectedEnd(x, y + callBack.headerHeight) + } + + fun cancelSelect() { + val last = if (ReadBookConfig.isScroll) 2 else 0 + for (relativePos in 0..last) { + relativePage(relativePos).textLines.forEach { textLine -> + textLine.textChars.forEach { + it.selected = false + } + } + } + invalidate() + callBack.onCancelSelect() } + val selectedText: String + get() { + val stringBuilder = StringBuilder() + for (relativePos in selectStart[0]..selectEnd[0]) { + val textPage = relativePage(relativePos) + if (relativePos == selectStart[0] && relativePos == selectEnd[0]) { + for (lineIndex in selectStart[1]..selectEnd[1]) { + if (lineIndex == selectStart[1] && lineIndex == selectEnd[1]) { + stringBuilder.append( + textPage.textLines[lineIndex].text.substring( + selectStart[2], + selectEnd[2] + 1 + ) + ) + } else if (lineIndex == selectStart[1]) { + stringBuilder.append( + textPage.textLines[lineIndex].text.substring( + selectStart[2] + ) + ) + } else if (lineIndex == selectEnd[1]) { + stringBuilder.append( + textPage.textLines[lineIndex].text.substring(0, selectEnd[2] + 1) + ) + } else { + stringBuilder.append(textPage.textLines[lineIndex].text) + } + } + } else if (relativePos == selectStart[0]) { + for (lineIndex in selectStart[1] until relativePage(relativePos).textLines.size) { + if (lineIndex == selectStart[1]) { + stringBuilder.append( + textPage.textLines[lineIndex].text.substring( + selectStart[2] + ) + ) + } else { + stringBuilder.append(textPage.textLines[lineIndex].text) + } + } + } else if (relativePos == selectEnd[0]) { + for (lineIndex in 0..selectEnd[1]) { + if (lineIndex == selectEnd[1]) { + stringBuilder.append( + textPage.textLines[lineIndex].text.substring(0, selectEnd[2] + 1) + ) + } else { + stringBuilder.append(textPage.textLines[lineIndex].text) + } + } + } else if (relativePos in selectStart[0] + 1 until selectEnd[0]) { + for (lineIndex in selectStart[1]..selectEnd[1]) { + stringBuilder.append(textPage.textLines[lineIndex].text) + } + } + } + return stringBuilder.toString() + } + + private fun selectToInt(page: Int, line: Int, char: Int): Int { + return page * 1000000 + line * 100000 + char + } + + private fun selectToInt(select: Array): Int { + return select[0] * 1000000 + select[1] * 100000 + select[2] + } + + private fun relativeOffset(relativePos: Int): Float { + return when (relativePos) { + 0 -> pageOffset + 1 -> pageOffset + textPage.height + else -> pageOffset + textPage.height + pageFactory.nextPage.height + } + } + + private fun relativePage(relativePos: Int): TextPage { + return when (relativePos) { + 0 -> textPage + 1 -> pageFactory.nextPage + else -> pageFactory.nextPagePlus + } + } + + interface CallBack { + fun upSelectedStart(x: Float, y: Float) + fun upSelectedEnd(x: Float, y: Float) + fun onCancelSelect() + val headerHeight: Int + val pageFactory: TextPageFactory + } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index 4f6a0dad4..7f1dcec1c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -3,78 +3,65 @@ package io.legado.app.ui.book.read.page import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.Drawable -import android.util.AttributeSet import android.view.MotionEvent import android.widget.FrameLayout +import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap import io.legado.app.R import io.legado.app.constant.AppConst.TIME_FORMAT -import io.legado.app.constant.PreferKey +import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig -import io.legado.app.ui.book.read.page.entities.SelectPoint import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_book_page.view.* import java.util.* -class ContentView : FrameLayout { - var callBack: CallBack? = null - private var headerHeight = 0 - private var pageSize: Int = 0 +class ContentView(context: Context) : FrameLayout(context) { - constructor(context: Context) : super(context) { - init() - } - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - init() - } - - fun init() { + init { //设置背景防止切换背景时文字重叠 setBackgroundColor(context.getCompatColor(R.color.background)) inflate(context, R.layout.view_book_page, this) + upStyle() upTime() + content_text_view.upView = { + tv_bottom_left.text = it.title + setPageIndex(it.index, it.pageSize) + } } fun upStyle() { - ReadBookConfig.durConfig.apply { + ReadBookConfig.apply { tv_top_left.typeface = ChapterProvider.typeface tv_top_right.typeface = ChapterProvider.typeface tv_bottom_left.typeface = ChapterProvider.typeface tv_bottom_right.typeface = ChapterProvider.typeface - if (context.getPrefBoolean(PreferKey.hideStatusBar, false)) { - //显示状态栏时隐藏header - ll_header.visible() - ll_header.layoutParams = - ll_header.layoutParams.apply { height = context.getStatusBarHeight() } + //显示状态栏时隐藏header + if (hideStatusBar) { + ll_header.layoutParams = ll_header.layoutParams.apply { + height = context.statusBarHeight + headerPaddingTop.dp + headerPaddingBottom.dp + } ll_header.setPadding( headerPaddingLeft.dp, headerPaddingTop.dp, headerPaddingRight.dp, headerPaddingBottom.dp ) - headerHeight = ll_header.height + ll_header.visible() page_panel.setPadding(0, 0, 0, 0) } else { ll_header.gone() - headerHeight = context.getStatusBarHeight() - page_panel.setPadding(0, headerHeight, 0, 0) + page_panel.setPadding(0, context.statusBarHeight, 0, 0) } - content_text_view.setPadding( - paddingLeft.dp, - paddingTop.dp, - paddingRight.dp, - paddingBottom.dp - ) ll_footer.setPadding( footerPaddingLeft.dp, footerPaddingTop.dp, footerPaddingRight.dp, footerPaddingBottom.dp ) - textColor().let { + content_text_view.upVisibleRect() + durConfig.textColor().let { tv_top_left.setTextColor(it) tv_top_right.setTextColor(it) tv_bottom_left.setTextColor(it) @@ -83,6 +70,15 @@ class ContentView : FrameLayout { } } + val headerHeight: Int + get() { + return if (ReadBookConfig.hideStatusBar) { + ll_header.height + } else { + context.statusBarHeight + } + } + fun setBg(bg: Drawable?) { page_panel.background = bg } @@ -95,60 +91,64 @@ class ContentView : FrameLayout { tv_top_right.text = context.getString(R.string.battery_show, battery) } - fun setContent(textPage: TextPage?) { - content_text_view.setContent(textPage) - if (textPage != null) { - tv_bottom_left.text = textPage.title - pageSize = textPage.pageSize - setPageIndex(textPage.index) + fun setContent(textPage: TextPage) { + tv_bottom_left.text = when (AppConfig.chineseConverterType) { + 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) + 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) + else -> textPage.title } + setPageIndex(textPage.index, textPage.pageSize) + content_text_view.resetPageOffset() + content_text_view.setContent(textPage) + } + + fun resetPageOffset() { + content_text_view.resetPageOffset() } @SuppressLint("SetTextI18n") - fun setPageIndex(pageIndex: Int?) { + fun setPageIndex(pageIndex: Int?, pageSize: Int) { pageIndex?.let { tv_bottom_right.text = "${pageIndex.plus(1)}/${pageSize}" } } + fun onScroll(offset: Float) { + content_text_view.onScroll(offset) + } + fun upSelectAble(selectAble: Boolean) { content_text_view.selectAble = selectAble } - fun selectText(e: MotionEvent): SelectPoint? { + fun selectText( + e: MotionEvent, + select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit + ) { val y = e.y - headerHeight - val selectPoint = content_text_view.selectText(e.x, y) - selectPoint?.let { - it.startY = it.startY + headerHeight - it.endY = it.endY + headerHeight - } - return selectPoint + return content_text_view.selectText(e.x, y, select) } fun selectStartMove(x: Float, y: Float) { - content_text_view.selectStartMove(x, y) + content_text_view.selectStartMove(x, y - headerHeight) } - fun selectEndMove(x: Float, y: Float) { - content_text_view.selectEndMove(x, y) + fun selectStartMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) { + content_text_view.selectStartMoveIndex(relativePage, lineIndex, charIndex) } - fun scrollTo(pos: Int?) { - if (pos != null) { - content_text_view.post { - - } - } + fun selectEndMove(x: Float, y: Float) { + content_text_view.selectEndMove(x, y - headerHeight) } - fun scrollToBottom() { - content_text_view.post { - - } + fun selectEndMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) { + content_text_view.selectEndMoveIndex(relativePage, lineIndex, charIndex) } - interface CallBack { - fun scrollToLine(line: Int) - fun scrollToLast() + fun cancelSelect() { + content_text_view.cancelSelect() } + + val selectedText: String get() = content_text_view.selectedText + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt index ed223a4cf..1d188f598 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt @@ -1,24 +1,19 @@ package io.legado.app.ui.book.read.page +import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.page.entities.TextChapter interface DataSource { - val isScrollDelegate: Boolean - val pageIndex: Int + val pageIndex: Int get() = ReadBook.durChapterPos() - fun setPageIndex(pageIndex: Int) + val currentChapter: TextChapter? - fun getChapterPosition(): Int + val nextChapter: TextChapter? - fun getCurrentChapter(): TextChapter? - - fun getNextChapter(): TextChapter? - - fun getPreviousChapter(): TextChapter? + val prevChapter: TextChapter? fun hasNextChapter(): Boolean fun hasPrevChapter(): Boolean - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt index 6f39f2992..85cdecbd5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt @@ -8,13 +8,15 @@ abstract class PageFactory(protected val dataSource: DataSource) { abstract fun moveToNext():Boolean - abstract fun moveToPrevious(): Boolean + abstract fun moveToPrev(): Boolean - abstract fun nextPage(): DATA? + abstract val nextPage: DATA - abstract fun previousPage(): DATA? + abstract val prevPage: DATA - abstract fun currentPage(): DATA? + abstract val currentPage: DATA + + abstract val nextPagePlus: DATA abstract fun hasNext(): Boolean diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 13a3322d2..1482d309a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -6,30 +6,26 @@ import android.graphics.Canvas import android.util.AttributeSet import android.view.MotionEvent import android.widget.FrameLayout -import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.page.delegate.* -import io.legado.app.ui.book.read.page.entities.SelectPoint import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.utils.activity -import io.legado.app.utils.getPrefInt class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs), - ContentView.CallBack, DataSource { - var callBack: CallBack? = null - var pageFactory: TextPageFactory? = null - private var pageDelegate: PageDelegate? = null + var callBack: CallBack + var pageFactory: TextPageFactory + var pageDelegate: PageDelegate? = null var prevPage: ContentView var curPage: ContentView var nextPage: ContentView init { - callBack = activity as? CallBack + callBack = activity as CallBack prevPage = ContentView(context) addView(prevPage) nextPage = ContentView(context) @@ -39,8 +35,7 @@ class PageView(context: Context, attrs: AttributeSet) : upBg() setWillNotDraw(false) pageFactory = TextPageFactory(this) - upPageAnim(context.getPrefInt(PreferKey.pageAnim)) - curPage.callBack = this + upPageAnim() } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { @@ -67,33 +62,34 @@ class PageView(context: Context, attrs: AttributeSet) : @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { - callBack?.screenOffTimerStart() - return pageDelegate?.onTouch(event) ?: super.onTouchEvent(event) + pageDelegate?.onTouch(event) + callBack.screenOffTimerStart() + return true + } + + fun onDestroy() { + pageDelegate?.onDestroy() + curPage.cancelSelect() } fun fillPage(direction: PageDelegate.Direction) { when (direction) { PageDelegate.Direction.PREV -> { - pageFactory?.moveToPrevious() + pageFactory.moveToPrev() upContent() - if (isScrollDelegate) { - curPage.scrollToBottom() - } } PageDelegate.Direction.NEXT -> { - pageFactory?.moveToNext() + pageFactory.moveToNext() upContent() - if (isScrollDelegate) { - curPage.scrollTo(0) - } } else -> Unit } } - fun upPageAnim(pageAnim: Int) { + fun upPageAnim() { + pageDelegate?.onDestroy() pageDelegate = null - pageDelegate = when (pageAnim) { + pageDelegate = when (ReadBookConfig.pageAnim) { 0 -> CoverPageDelegate(this) 1 -> SlidePageDelegate(this) 2 -> SimulationPageDelegate(this) @@ -103,36 +99,26 @@ class PageView(context: Context, attrs: AttributeSet) : upContent() } - fun upContent(position: Int = 0) { - pageFactory?.let { - when (position) { - -1 -> prevPage.setContent(it.previousPage()) - 1 -> nextPage.setContent(it.nextPage()) + fun upContent(relativePosition: Int = 0) { + if (ReadBookConfig.isScroll) { + curPage.setContent(pageFactory.currentPage) + } else { + when (relativePosition) { + -1 -> prevPage.setContent(pageFactory.prevPage) + 1 -> nextPage.setContent(pageFactory.nextPage) else -> { - curPage.setContent(it.currentPage()) - nextPage.setContent(it.nextPage()) - prevPage.setContent(it.previousPage()) - if (isScrollDelegate) { - curPage.scrollTo(ReadBook.textChapter()?.getStartLine(ReadBook.durChapterPos())) - } + curPage.setContent(pageFactory.currentPage) + nextPage.setContent(pageFactory.nextPage) + prevPage.setContent(pageFactory.prevPage) } } - if (isScrollDelegate) { - prevPage.scrollToBottom() - } } - callBack?.screenOffTimerStart() + callBack.screenOffTimerStart() } fun moveToPrevPage(noAnim: Boolean = true) { if (noAnim) { - if (isScrollDelegate) { - ReadBook.textChapter()?.let { - curPage.scrollTo(it.getStartLine(pageIndex - 1)) - } - } else { - fillPage(PageDelegate.Direction.PREV) - } + fillPage(PageDelegate.Direction.PREV) } else { pageDelegate?.start(PageDelegate.Direction.PREV) } @@ -140,24 +126,14 @@ class PageView(context: Context, attrs: AttributeSet) : fun moveToNextPage(noAnim: Boolean = true) { if (noAnim) { - if (isScrollDelegate) { - ReadBook.textChapter()?.let { - curPage.scrollTo(it.getStartLine(pageIndex + 1)) - } - } else { - fillPage(PageDelegate.Direction.NEXT) - } + fillPage(PageDelegate.Direction.NEXT) } else { pageDelegate?.start(PageDelegate.Direction.NEXT) } } - fun upSelectAble(selectAble: Boolean) { - pageDelegate?.upSelectAble(selectAble) - curPage.upSelectAble(selectAble) - } - fun upStyle() { + ChapterProvider.upStyle() curPage.upStyle() prevPage.upStyle() nextPage.upStyle() @@ -184,38 +160,19 @@ class PageView(context: Context, attrs: AttributeSet) : nextPage.upBattery(battery) } - fun selectStartMove(x: Float, y: Float) { - curPage.selectStartMove(x, y) - } - - fun selectEndMove(x: Float, y: Float) { - curPage.selectEndMove(x, y) - } - - override val isScrollDelegate: Boolean - get() = pageDelegate is ScrollPageDelegate - - override val pageIndex: Int - get() = ReadBook.durChapterPos() - - override fun setPageIndex(pageIndex: Int) { - callBack?.setPageIndex(pageIndex) + override val currentChapter: TextChapter? + get() { + return if (callBack.isInitFinish) ReadBook.textChapter(0) else null } - override fun getChapterPosition(): Int { - return ReadBook.durChapterIndex + override val nextChapter: TextChapter? + get() { + return if (callBack.isInitFinish) ReadBook.textChapter(1) else null } - override fun getCurrentChapter(): TextChapter? { - return if (callBack?.isInitFinish == true) ReadBook.textChapter(0) else null - } - - override fun getNextChapter(): TextChapter? { - return if (callBack?.isInitFinish == true) ReadBook.textChapter(1) else null - } - - override fun getPreviousChapter(): TextChapter? { - return if (callBack?.isInitFinish == true) ReadBook.textChapter(-1) else null + override val prevChapter: TextChapter? + get() { + return if (callBack.isInitFinish) ReadBook.textChapter(-1) else null } override fun hasNextChapter(): Boolean { @@ -223,47 +180,12 @@ class PageView(context: Context, attrs: AttributeSet) : } override fun hasPrevChapter(): Boolean { - callBack?.let { - return ReadBook.durChapterIndex > 0 - } - return false - } - - override fun scrollToLine(line: Int) { - if (isScrollDelegate) { - ReadBook.textChapter()?.let { - val pageIndex = it.getPageIndex(line) - curPage.setPageIndex(pageIndex) - callBack?.setPageIndex(pageIndex) - } - } - } - - override fun scrollToLast() { - if (isScrollDelegate) { - ReadBook.textChapter()?.let { - callBack?.setPageIndex(it.lastIndex()) - curPage.setPageIndex(it.lastIndex()) - } - } + return ReadBook.durChapterIndex > 0 } interface CallBack { - val isInitFinish: Boolean - - /** - * 保存页数 - */ - fun setPageIndex(pageIndex: Int) - - /** - * 点击屏幕中间 - */ fun clickCenter() - fun screenOffTimerStart() - - fun selectText(selectPoint: SelectPoint) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt index 03a222b94..fe18f1b26 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt @@ -6,91 +6,107 @@ import io.legado.app.ui.book.read.page.entities.TextPage class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource) { override fun hasPrev(): Boolean = with(dataSource) { - return if (isScrollDelegate) { - hasPrevChapter() - } else { - hasPrevChapter() || pageIndex > 0 - } + return hasPrevChapter() || pageIndex > 0 } override fun hasNext(): Boolean = with(dataSource) { - return if (isScrollDelegate) { - hasNextChapter() - } else { - hasNextChapter() - || getCurrentChapter()?.isLastIndex(pageIndex) != true - } + return hasNextChapter() || currentChapter?.isLastIndex(pageIndex) != true } override fun moveToFirst() { - dataSource.setPageIndex(0) + ReadBook.setPageIndex(0) } override fun moveToLast() = with(dataSource) { - getCurrentChapter()?.let { + currentChapter?.let { if (it.pageSize() == 0) { - setPageIndex(0) + ReadBook.setPageIndex(0) } else { - setPageIndex(it.pageSize().minus(1)) + ReadBook.setPageIndex(it.pageSize().minus(1)) } - } ?: setPageIndex(0) + } ?: ReadBook.setPageIndex(0) } override fun moveToNext(): Boolean = with(dataSource) { return if (hasNext()) { - if (getCurrentChapter()?.isLastIndex(pageIndex) == true - || isScrollDelegate - ) { + if (currentChapter?.isLastIndex(pageIndex) == true) { ReadBook.moveToNextChapter(false) } else { - setPageIndex(pageIndex.plus(1)) + ReadBook.setPageIndex(pageIndex.plus(1)) } true } else false } - override fun moveToPrevious(): Boolean = with(dataSource) { + override fun moveToPrev(): Boolean = with(dataSource) { return if (hasPrev()) { - if (pageIndex <= 0 || isScrollDelegate) { + if (pageIndex <= 0) { ReadBook.moveToPrevChapter(false) } else { - setPageIndex(pageIndex.minus(1)) + ReadBook.setPageIndex(pageIndex.minus(1)) } true } else false } - override fun currentPage(): TextPage? = with(dataSource) { - return if (isScrollDelegate) { - getCurrentChapter()?.scrollPage() - } else { - getCurrentChapter()?.page(pageIndex) + override val currentPage: TextPage + get() = with(dataSource) { + currentChapter?.let { + return@with it.page(pageIndex) + ?: TextPage(title = it.title).format() + } + return TextPage().format() } - } - override fun nextPage(): TextPage? = with(dataSource) { - if (isScrollDelegate) { - return getNextChapter()?.scrollPage() - } - getCurrentChapter()?.let { - if (pageIndex < it.pageSize() - 1) { - return getCurrentChapter()?.page(pageIndex + 1)?.removePageAloudSpan() + override val nextPage: TextPage + get() = with(dataSource) { + currentChapter?.let { + if (pageIndex < it.pageSize() - 1) { + return@with it.page(pageIndex + 1)?.removePageAloudSpan() + ?: TextPage(title = it.title).format() + } + } + nextChapter?.let { + return@with it.page(0)?.removePageAloudSpan() + ?: TextPage(title = it.title).format() } + return TextPage().format() } - return getNextChapter()?.page(0)?.removePageAloudSpan() - } - override fun previousPage(): TextPage? = with(dataSource) { - if (isScrollDelegate) { - return getPreviousChapter()?.scrollPage() - } - if (pageIndex > 0) { - return getCurrentChapter()?.page(pageIndex - 1)?.removePageAloudSpan() + override val prevPage: TextPage + get() = with(dataSource) { + if (pageIndex > 0) { + currentChapter?.let { + return@with it.page(pageIndex - 1)?.removePageAloudSpan() + ?: TextPage(title = it.title).format() + } + } + prevChapter?.let { + return@with it.lastPage()?.removePageAloudSpan() + ?: TextPage(title = it.title).format() + } + return TextPage().format() } - return getPreviousChapter()?.lastPage()?.removePageAloudSpan() - } + override val nextPagePlus: TextPage + get() = with(dataSource) { + currentChapter?.let { + if (pageIndex < it.pageSize() - 2) { + return@with it.page(pageIndex + 2)?.removePageAloudSpan() + ?: TextPage(title = it.title).format() + } + nextChapter?.let { nc -> + if (pageIndex < it.pageSize() - 1) { + return@with nc.page(0)?.removePageAloudSpan() + ?: TextPage(title = nc.title).format() + } + return@with nc.page(1)?.removePageAloudSpan() + ?: TextPage(title = nc.title).format() + } + } + return TextPage().format() + } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt index f129d41ff..586b3d88e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt @@ -6,19 +6,30 @@ import kotlin.math.abs abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) { - override fun onScroll( - e1: MotionEvent, - e2: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { + override fun onTouch(event: MotionEvent) { + when (event.action) { + MotionEvent.ACTION_MOVE -> { + if (isTextSelected) { + selectText(event) + } else { + onScroll(event) + } + } + } + super.onTouch(event) + } + + private fun onScroll(event: MotionEvent) { + //判断是否移动了 if (!isMoved) { - if (abs(distanceX) > abs(distanceY)) { - if (distanceX < 0) { + isMoved = abs(startX - event.x) > slop + || abs(startX - event.x) > abs(startY - event.y) + if (isMoved) { + if (event.x - startX > 0) { //如果上一页不存在 if (!hasPrev()) { noNext = true - return true + return } setDirection(Direction.PREV) setBitmap() @@ -26,21 +37,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie //如果不存在表示没有下一页了 if (!hasNext()) { noNext = true - return true + return } setDirection(Direction.NEXT) setBitmap() } - isMoved = true } } if (isMoved) { - isCancel = if (mDirection == Direction.NEXT) distanceX < 0 else distanceX > 0 + isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX isRunning = true //设置触摸点 - setTouchPoint(e2.x, e2.y) + setTouchPoint(event.x, event.y) } - return isMoved } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index dc3b9dddd..2b26714c4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -6,15 +6,14 @@ import android.graphics.Canvas import android.graphics.RectF import android.view.GestureDetector import android.view.MotionEvent +import android.view.ViewConfiguration +import android.view.animation.DecelerateInterpolator import android.widget.Scroller import androidx.annotation.CallSuper -import androidx.interpolator.view.animation.FastOutLinearInInterpolator import com.google.android.material.snackbar.Snackbar -import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.PageView -import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.screenshot import kotlin.math.abs @@ -25,42 +24,36 @@ abstract class PageDelegate(protected val pageView: PageView) : pageView.width * 0.66f, pageView.height * 0.66f ) protected val context: Context = pageView.context + protected val slop = ViewConfiguration.get(context).scaledTouchSlop //起始点 - protected var startX: Float = 0.toFloat() - protected var startY: Float = 0.toFloat() + protected var startX: Float = 0f + protected var startY: Float = 0f + //上一个触碰点 + protected var lastX: Float = 0f + protected var lastY: Float = 0f //触碰点 - protected var touchX: Float = 0.toFloat() - protected var touchY: Float = 0.toFloat() + protected var touchX: Float = 0f + protected var touchY: Float = 0f - protected val nextPage: ContentView - get() = pageView.nextPage - - protected val curPage: ContentView - get() = pageView.curPage - - protected val prevPage: ContentView - get() = pageView.prevPage + protected val nextPage: ContentView get() = pageView.nextPage + protected val curPage: ContentView get() = pageView.curPage + protected val prevPage: ContentView get() = pageView.prevPage protected var bitmap: Bitmap? = null protected var viewWidth: Int = pageView.width protected var viewHeight: Int = pageView.height - //textView在顶端或低端 - protected var atTop: Boolean = false - protected var atBottom: Boolean = false private val snackBar: Snackbar by lazy { Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT) } private val scroller: Scroller by lazy { - Scroller(pageView.context, FastOutLinearInInterpolator()) + Scroller(pageView.context, DecelerateInterpolator()) } private val detector: GestureDetector by lazy { - GestureDetector(pageView.context, this).apply { - setIsLongpressEnabled(context.getPrefBoolean(PreferKey.textSelectAble)) - } + GestureDetector(pageView.context, this) } var isMoved = false @@ -71,32 +64,50 @@ abstract class PageDelegate(protected val pageView: PageView) : var isCancel = false var isRunning = false var isStarted = false + var isTextSelected = false + var selectedOnDown = false + + var firstRelativePage = 0 + var firstLineIndex: Int = 0 + var firstCharIndex: Int = 0 + + init { + curPage.resetPageOffset() + } open fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) { startX = x startY = y + lastX = x + lastY = y + touchX = x + touchY = y if (invalidate) { - invalidate() + pageView.invalidate() } } open fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) { + lastX = touchX + lastY = touchY touchX = x touchY = y if (invalidate) { - invalidate() + pageView.invalidate() } onScroll() } - fun upSelectAble(selectAble: Boolean) { - detector.setIsLongpressEnabled(selectAble) - } - - protected fun invalidate() { + open fun fling( + startX: Int, startY: Int, velocityX: Int, velocityY: Int, + minX: Int, maxX: Int, minY: Int, maxY: Int + ) { + scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY) + isRunning = true + isStarted = true pageView.invalidate() } @@ -110,28 +121,21 @@ abstract class PageDelegate(protected val pageView: PageView) : ) isRunning = true isStarted = true - invalidate() + pageView.invalidate() } - protected fun stopScroll() { + private fun stopScroll() { isRunning = false isStarted = false - invalidate() - if (pageView.isScrollDelegate) { - pageView.postDelayed({ - bitmap?.recycle() - bitmap = null - }, 100) - } else { - bitmap?.recycle() - bitmap = null - } + pageView.invalidate() + bitmap?.recycle() + bitmap = null } - fun setViewSize(width: Int, height: Int) { + open fun setViewSize(width: Int, height: Int) { viewWidth = width viewHeight = height - invalidate() + pageView.invalidate() centerRectF.set( width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f @@ -142,7 +146,6 @@ abstract class PageDelegate(protected val pageView: PageView) : if (scroller.computeScrollOffset()) { setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) } else if (isStarted) { - setTouchPoint(scroller.finalX.toFloat(), scroller.finalY.toFloat(), false) onAnimStop() stopScroll() } @@ -182,14 +185,13 @@ abstract class PageDelegate(protected val pageView: PageView) : onAnimStart() } - abstract fun onAnimStart()//scroller start + open fun onAnimStart() {}//scroller start - abstract fun onDraw(canvas: Canvas)//绘制 + open fun onDraw(canvas: Canvas) {}//绘制 - abstract fun onAnimStop()//scroller finish + open fun onAnimStop() {}//scroller finish - open fun onScroll() {//移动contentView, slidePage - } + open fun onScroll() {}//移动contentView, slidePage @CallSuper open fun setDirection(direction: Direction) { @@ -208,17 +210,28 @@ abstract class PageDelegate(protected val pageView: PageView) : * 触摸事件处理 */ @CallSuper - open fun onTouch(event: MotionEvent): Boolean { - if (isStarted) return false - curPage.dispatchTouchEvent(event) - return detector.onTouchEvent(event) + open fun onTouch(event: MotionEvent) { + if (isStarted) return + if (!detector.onTouchEvent(event)) { + //GestureDetector.onFling小幅移动不会触发,所以要自己判断 + if (event.action == MotionEvent.ACTION_UP && isMoved) { + if (selectedOnDown) { + selectedOnDown = false + } + if (!noNext) onAnimStart() + } + } } /** * 按下 */ override fun onDown(e: MotionEvent): Boolean { -// abort() + if (isTextSelected) { + curPage.cancelSelect() + isTextSelected = false + selectedOnDown = true + } //是否移动 isMoved = false //是否存在下一章 @@ -238,25 +251,25 @@ abstract class PageDelegate(protected val pageView: PageView) : * 单击 */ override fun onSingleTapUp(e: MotionEvent): Boolean { + if (selectedOnDown) { + selectedOnDown = false + return true + } val x = e.x val y = e.y if (centerRectF.contains(x, y)) { - pageView.callBack?.clickCenter() + pageView.callBack.clickCenter() setTouchPoint(x, y) } else { if (x > viewWidth / 2 || AppConfig.clickAllNext ) { //设置动画方向 - if (!hasNext()) { - return true - } + if (!hasNext()) return true setDirection(Direction.NEXT) setBitmap() } else { - if (!hasPrev()) { - return true - } + if (!hasPrev()) return true setDirection(Direction.PREV) setBitmap() } @@ -270,30 +283,50 @@ abstract class PageDelegate(protected val pageView: PageView) : * 长按选择 */ override fun onLongPress(e: MotionEvent) { - val textChar = curPage.selectText(e) - textChar?.let { - pageView.callBack?.selectText(it) + curPage.selectText(e) { relativePage, lineIndex, charIndex -> + isTextSelected = true + firstRelativePage = relativePage + firstLineIndex = lineIndex + firstCharIndex = charIndex } } - /** - * 移动结束 - */ - override fun onFling( - e1: MotionEvent?, - e2: MotionEvent?, - velocityX: Float, - velocityY: Float - ): Boolean { - if (!noNext) onAnimStart() - return true + protected fun selectText(event: MotionEvent) { + curPage.selectText(event) { relativePage, lineIndex, charIndex -> + when { + relativePage > firstRelativePage -> { + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) + } + relativePage < firstRelativePage -> { + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) + } + lineIndex > firstLineIndex -> { + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) + } + lineIndex < firstLineIndex -> { + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) + } + charIndex > firstCharIndex -> { + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) + } + else -> { + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) + } + } + } } /** * 判断是否有上一页 */ fun hasPrev(): Boolean { - val hasPrev = pageView.pageFactory?.hasPrev() == true + val hasPrev = pageView.pageFactory.hasPrev() if (!hasPrev) { if (!snackBar.isShown) { snackBar.setText("没有上一页") @@ -307,7 +340,7 @@ abstract class PageDelegate(protected val pageView: PageView) : * 判断是否有下一页 */ fun hasNext(): Boolean { - val hasNext = pageView.pageFactory?.hasNext() == true + val hasNext = pageView.pageFactory.hasNext() if (!hasNext) { if (!snackBar.isShown) { snackBar.setText("没有下一页") @@ -317,24 +350,12 @@ abstract class PageDelegate(protected val pageView: PageView) : return hasNext } + open fun onDestroy() { + bitmap?.recycle() + } + enum class Direction { NONE, PREV, NEXT } - fun MotionEvent.toAction(action: Int): MotionEvent { - return MotionEvent.obtain( - downTime, - eventTime, - action, - x, - y, - pressure, - size, - metaState, - xPrecision, - yPrecision, - deviceId, - edgeFlags - ) - } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index 9c9aade48..d90ee8649 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -1,119 +1,61 @@ package io.legado.app.ui.book.read.page.delegate -import android.graphics.Canvas -import android.graphics.Matrix import android.view.MotionEvent +import android.view.VelocityTracker import io.legado.app.ui.book.read.page.PageView import kotlin.math.abs class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { - private val bitmapMatrix = Matrix() + // 滑动追踪的时间 + private val velocityDuration = 1000 + //速度追踪器 + private val mVelocity: VelocityTracker = VelocityTracker.obtain() override fun onAnimStart() { - if (!atTop && !atBottom) { - stopScroll() - return - } - val distanceY: Float - when (mDirection) { - Direction.NEXT -> distanceY = - if (isCancel) { - var dis = viewHeight - startY + touchY - if (dis > viewHeight) { - dis = viewHeight.toFloat() - } - viewHeight - dis - } else { - -(touchY + (viewHeight - startY)) - } - else -> distanceY = - if (isCancel) { - -(touchY - startY) - } else { - viewHeight - (touchY - startY) - } - } - - startScroll(0, touchY.toInt(), 0, distanceY.toInt()) + //惯性滚动 + fling( + 0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(), + 0, 0, -10 * viewHeight, 10 * viewHeight + ) } - override fun onDraw(canvas: Canvas) { - if (atTop || atBottom) { - val offsetY = touchY - startY - - if ((mDirection == Direction.NEXT && offsetY > 0) - || (mDirection == Direction.PREV && offsetY < 0) - ) return + override fun onScroll() { + curPage.onScroll(touchY - lastY) + } - val distanceY = if (offsetY > 0) offsetY - viewHeight else offsetY + viewHeight - if (atTop && mDirection == Direction.PREV) { - bitmap?.let { - bitmapMatrix.setTranslate(0.toFloat(), distanceY) - canvas.drawBitmap(it, bitmapMatrix, null) - } - } else if (atBottom && mDirection == Direction.NEXT) { - bitmap?.let { - bitmapMatrix.setTranslate(0.toFloat(), distanceY) - canvas.drawBitmap(it, bitmapMatrix, null) + override fun onTouch(event: MotionEvent) { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + setStartPoint(event.x, event.y) + abort() + mVelocity.clear() + } + MotionEvent.ACTION_MOVE -> { + if (isTextSelected) { + selectText(event) + } else { + onScroll(event) } } } + super.onTouch(event) } - override fun onAnimStop() { - if (!isCancel) { - pageView.fillPage(mDirection) - } - } - - override fun onScroll( - e1: MotionEvent, - e2: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - if (!isMoved && abs(distanceX) < abs(distanceY)) { - if (distanceY < 0) { - if (atTop) { - val event = e1.toAction(MotionEvent.ACTION_UP) - curPage.dispatchTouchEvent(event) - event.recycle() - //如果上一页不存在 - if (!hasPrev()) { - noNext = true - return true - } - setDirection(Direction.PREV) - setBitmap() - } - } else { - if (atBottom) { - val event = e1.toAction(MotionEvent.ACTION_UP) - curPage.dispatchTouchEvent(event) - event.recycle() - //如果不存在表示没有下一页了 - if (!hasNext()) { - noNext = true - return true - } - setDirection(Direction.NEXT) - setBitmap() - } - } - isMoved = true - } - if ((atTop && mDirection != Direction.PREV) || (atBottom && mDirection != Direction.NEXT) || mDirection == Direction.NONE) { - //传递触摸事件到textView - curPage.dispatchTouchEvent(e2) + private fun onScroll(event: MotionEvent) { + mVelocity.addMovement(event) + mVelocity.computeCurrentVelocity(velocityDuration) + setTouchPoint(event.x, event.y) + if (!isMoved) { + isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop } if (isMoved) { - isCancel = if (mDirection == Direction.NEXT) distanceY < 0 else distanceY > 0 isRunning = true - //设置触摸点 - setTouchPoint(e2.x, e2.y) } - return isMoved } + override fun onDestroy() { + super.onDestroy() + mVelocity.recycle() + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt index 789fb61e1..6d7dd7bf8 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt @@ -40,7 +40,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi private var mMiddleY = 0f private var mDegrees = 0f private var mTouchToCornerDis = 0f - private var mColorMatrixFilter: ColorMatrixColorFilter = ColorMatrixColorFilter( + private var mColorMatrixFilter = ColorMatrixColorFilter( ColorMatrix( floatArrayOf( 1f, 0f, 0f, 0f, 0f, @@ -55,7 +55,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi // 是否属于右上左下 private var mIsRtOrLb = false - private var mMaxLength = 0f + private var mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() // 背面颜色组 private var mBackShadowColors: IntArray // 前面颜色组 @@ -71,15 +71,13 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi private var mFrontShadowDrawableVLR: GradientDrawable private var mFrontShadowDrawableVRL: GradientDrawable - private val mPaint: Paint = Paint() + private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL } private var curBitmap: Bitmap? = null private var prevBitmap: Bitmap? = null private var nextBitmap: Bitmap? = null init { - mMaxLength = hypot(viewWidth.toDouble(), viewWidth.toDouble()).toFloat() - mPaint.style = Paint.Style.FILL //设置颜色数组 val color = intArrayOf(0x333333, -0x4fcccccd) mFolderShadowDrawableRL = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color) @@ -115,13 +113,18 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mFrontShadowDrawableHBT.gradientType = GradientDrawable.LINEAR_GRADIENT } + override fun setViewSize(width: Int, height: Int) { + super.setViewSize(width, height) + mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() + } + override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) { super.setStartPoint(x, y, invalidate) calcCornerXY(x, y) } override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) { - super.setTouchPoint(x, y, invalidate) + super.setTouchPoint(x, y, false) //触摸y中间位置吧y变成屏幕高度 if ((startY > viewHeight * 0.33 && startY < viewHeight * 0.66) || mDirection == Direction.PREV @@ -134,6 +137,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi ) { touchY = 1f } + pageView.invalidate() } override fun setDirection(direction: Direction) { @@ -171,7 +175,6 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi override fun onAnimStart() { var dx: Float val dy: Float - // dx 水平方向滑动的距离,负值会使滚动向左滚动 // dy 垂直方向滑动的距离,负值会使滚动向上滚动 if (isCancel) { dx = if (mCornerX > 0 && mDirection == Direction.NEXT) { @@ -215,18 +218,22 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi } override fun onDraw(canvas: Canvas) { - if (mDirection === Direction.NEXT) { - calcPoints() - drawCurrentPageArea(canvas, curBitmap, mPath0) - drawNextPageAreaAndShadow(canvas, nextBitmap) - drawCurrentPageShadow(canvas) - drawCurrentBackArea(canvas, curBitmap) - } else { - calcPoints() - drawCurrentPageArea(canvas, prevBitmap, mPath0) - drawNextPageAreaAndShadow(canvas, curBitmap) - drawCurrentPageShadow(canvas) - drawCurrentBackArea(canvas, prevBitmap) + when (mDirection) { + Direction.NEXT -> { + calcPoints() + drawCurrentPageArea(canvas, curBitmap) + drawNextPageAreaAndShadow(canvas, nextBitmap) + drawCurrentPageShadow(canvas) + drawCurrentBackArea(canvas, curBitmap) + } + Direction.PREV -> { + calcPoints() + drawCurrentPageArea(canvas, prevBitmap) + drawNextPageAreaAndShadow(canvas, curBitmap) + drawCurrentPageShadow(canvas) + drawCurrentBackArea(canvas, prevBitmap) + } + else -> return } } @@ -254,7 +261,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi val left: Int val right: Int if (mIsRtOrLb) { - left = mBezierStart1.x.toInt() - 1 + left = (mBezierStart1.x - 1).toInt() right = (mBezierStart1.x + f3 + 1).toInt() mFolderShadowDrawable = mFolderShadowDrawableLR } else { @@ -333,17 +340,16 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi var mCurrentPageShadow: GradientDrawable if (mIsRtOrLb) { leftX = mBezierControl1.x.toInt() - rightX = mBezierControl1.x.toInt() + 25 + rightX = (mBezierControl1.x + 25).toInt() mCurrentPageShadow = mFrontShadowDrawableVLR } else { - leftX = mBezierControl1.x.toInt() - 25 - rightX = mBezierControl1.x.toInt() + 1 + leftX = (mBezierControl1.x - 25).toInt() + rightX = (mBezierControl1.x + 1).toInt() mCurrentPageShadow = mFrontShadowDrawableVRL } - var rotateDegrees: Float = - Math.toDegrees( - atan2(mTouchX - mBezierControl1.x, mBezierControl1.y - mTouchY).toDouble() - ).toFloat() + var rotateDegrees = Math.toDegrees( + atan2(mTouchX - mBezierControl1.x, mBezierControl1.y - mTouchY).toDouble() + ).toFloat() canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y) mCurrentPageShadow.setBounds( leftX, (mBezierControl1.y - mMaxLength).toInt(), @@ -368,24 +374,25 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi if (mIsRtOrLb) { leftX = mBezierControl2.y.toInt() - rightX = mBezierControl2.y.toInt() + 25 + rightX = (mBezierControl2.y + 25).toInt() mCurrentPageShadow = mFrontShadowDrawableHTB } else { - leftX = mBezierControl2.y.toInt() - 25 - rightX = mBezierControl2.y.toInt() + 1 + leftX = (mBezierControl2.y - 25).toInt() + rightX = (mBezierControl2.y + 1).toInt() mCurrentPageShadow = mFrontShadowDrawableHBT } rotateDegrees = Math.toDegrees( atan2(mBezierControl2.y - mTouchY, mBezierControl2.x - mTouchX).toDouble() ).toFloat() canvas.rotate(rotateDegrees, mBezierControl2.x, mBezierControl2.y) - val temp: Float = - if (mBezierControl2.y < 0) mBezierControl2.y - viewHeight else mBezierControl2.y - val hmg = hypot(mBezierControl2.x.toDouble(), temp.toDouble()).toInt() + val temp = + if (mBezierControl2.y < 0) (mBezierControl2.y - viewHeight).toDouble() + else mBezierControl2.y.toDouble() + val hmg = hypot(mBezierControl2.x.toDouble(), temp) if (hmg > mMaxLength) mCurrentPageShadow.setBounds( - (mBezierControl2.x - 25).toInt() - hmg, leftX, - (mBezierControl2.x + mMaxLength).toInt() - hmg, rightX + (mBezierControl2.x - 25 - hmg).toInt(), leftX, + (mBezierControl2.x + mMaxLength - hmg).toInt(), rightX ) else mCurrentPageShadow.setBounds( @@ -396,6 +403,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi canvas.restore() } + // private fun drawNextPageAreaAndShadow( canvas: Canvas, bitmap: Bitmap? @@ -443,10 +451,10 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi canvas.restore() } + // private fun drawCurrentPageArea( canvas: Canvas, - bitmap: Bitmap?, - path: Path + bitmap: Bitmap? ) { bitmap ?: return mPath0.reset() @@ -457,11 +465,12 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mPath0.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, mBezierStart2.y) mPath0.lineTo(mCornerX.toFloat(), mCornerY.toFloat()) mPath0.close() + canvas.save() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - canvas.clipOutPath(path) + canvas.clipOutPath(mPath0) } else { - canvas.clipPath(path, Region.Op.XOR) + canvas.clipPath(mPath0, Region.Op.XOR) } canvas.drawBitmap(bitmap, 0f, 0f, null) canvas.restore() @@ -480,8 +489,10 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi private fun calcPoints() { mTouchX = touchX mTouchY = touchY + mMiddleX = (mTouchX + mCornerX) / 2 mMiddleY = (mTouchY + mCornerY) / 2 + mBezierControl1.x = mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX) mBezierControl1.y = mCornerY.toFloat() @@ -492,6 +503,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi } else { mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) } + mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2 mBezierStart1.y = mCornerY.toFloat() //固定左边上下两个点 @@ -500,24 +512,27 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi if (mBezierStart1.x < 0) mBezierStart1.x = viewWidth - mBezierStart1.x - val f1: Float = abs(mCornerX - mTouchX) - val f2: Float = viewWidth * f1 / mBezierStart1.x + val f1 = abs(mCornerX - mTouchX) + val f2 = viewWidth * f1 / mBezierStart1.x mTouchX = abs(mCornerX - f2) - val f3: Float = abs(mCornerX - mTouchX) * abs(mCornerY - mTouchY) / f1 + val f3 = abs(mCornerX - mTouchX) * abs(mCornerY - mTouchY) / f1 mTouchY = abs(mCornerY - f3) mMiddleX = (mTouchX + mCornerX) / 2 mMiddleY = (mTouchY + mCornerY) / 2 + mBezierControl1.x = mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX) mBezierControl1.y = mCornerY.toFloat() + mBezierControl2.x = mCornerX.toFloat() mBezierControl2.y = if ((mCornerY - mMiddleY).toInt() == 0) { mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f } else { mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) } - mBezierStart1.x = (mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2) + + mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2 } } mBezierStart2.x = mCornerX.toFloat() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/SelectPoint.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/SelectPoint.kt deleted file mode 100644 index 0be88dae6..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/SelectPoint.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.legado.app.ui.book.read.page.entities - -data class SelectPoint( - var startX: Float, - var startY: Float, - var endX: Float, - var endY: Float -) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt index 9883e6dab..72798a9ea 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt @@ -2,7 +2,7 @@ package io.legado.app.ui.book.read.page.entities data class TextChar( val charData: String, - var selected: Boolean = false, - val leftBottomPosition: TextPoint, - val rightTopPosition: TextPoint + var start: Float, + var end: Float, + var selected: Boolean = false ) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index c5282aba3..a9c84fb68 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -3,8 +3,26 @@ package io.legado.app.ui.book.read.page.entities data class TextLine( var text: String = "", val textChars: ArrayList = arrayListOf(), - var lineTop: Int = 0, - var lineBottom: Int = 0, + var lineTop: Float = 0f, + var lineBase: Float = 0f, + var lineBottom: Float = 0f, val isTitle: Boolean = false, var isReadAloud: Boolean = false -) \ No newline at end of file +) { + + fun addTextChar(charData: String, start: Float, end: Float) { + textChars.add(TextChar(charData, start = start, end = end)) + } + + fun getTextCharAt(index: Int): TextChar { + return textChars[index] + } + + fun getTextCharReverseAt(index: Int): TextChar { + return textChars[textChars.lastIndex - index] + } + + fun getTextCharsCount(): Int { + return textChars.size + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 653f352e5..4e6089ddd 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -1,7 +1,10 @@ package io.legado.app.ui.book.read.page.entities +import android.text.Layout +import android.text.StaticLayout import io.legado.app.App import io.legado.app.R +import io.legado.app.ui.book.read.page.ChapterProvider data class TextPage( var index: Int = 0, @@ -10,9 +13,44 @@ data class TextPage( val textLines: ArrayList = arrayListOf(), var pageSize: Int = 0, var chapterSize: Int = 0, - var chapterIndex: Int = 0 + var chapterIndex: Int = 0, + var height: Int = 0 ) { + @Suppress("DEPRECATION") + fun format(): TextPage { + if (textLines.isEmpty() && ChapterProvider.visibleWidth > 0) { + val layout = StaticLayout( + text, ChapterProvider.contentPaint, ChapterProvider.visibleWidth, + Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false + ) + var y = (ChapterProvider.visibleHeight - layout.height) / 2f + if (y < 0) y = 0f + for (lineIndex in 0 until layout.lineCount) { + val textLine = TextLine() + textLine.lineTop = (ChapterProvider.paddingTop + y - + (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))) + textLine.lineBase = (ChapterProvider.paddingTop + y - + (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))) + textLine.lineBottom = + textLine.lineBase + ChapterProvider.contentPaint.fontMetrics.descent + var x = (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2 + textLine.text = + text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) + for (i in textLine.text.indices) { + val char = textLine.text[i].toString() + val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint) + val x1 = x + cw + textLine.textChars.add(TextChar(charData = char, start = x, end = x1)) + x = x1 + } + textLines.add(textLine) + } + height = ChapterProvider.visibleHeight + } + return this + } + fun removePageAloudSpan(): TextPage { textLines.forEach { textLine -> textLine.isReadAloud = false diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPoint.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPoint.kt deleted file mode 100644 index 44b81c2a9..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPoint.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.legado.app.ui.book.read.page.entities - -data class TextPoint( - val x: Float, - val y: Int -) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/BookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/search/BookAdapter.kt index 070837520..05ea3c782 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/BookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/BookAdapter.kt @@ -5,11 +5,11 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.Book -import kotlinx.android.synthetic.main.item_text.view.* +import kotlinx.android.synthetic.main.item_fillet_text.view.* import org.jetbrains.anko.sdk27.listeners.onClick class BookAdapter(context: Context, val callBack: CallBack) : - SimpleRecyclerAdapter(context, R.layout.item_text) { + SimpleRecyclerAdapter(context, R.layout.item_fillet_text) { override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { with(holder.itemView) { diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 8db633cf8..07a3b16c5 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -16,10 +16,6 @@ class DiffCallBack(private val oldItems: List, private val newItems: } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return true - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] if (oldItem.name != newItem.name) { @@ -28,7 +24,13 @@ class DiffCallBack(private val oldItems: List, private val newItems: if (oldItem.author != newItem.author) { return false } - if (oldItem.origins?.size != newItem.origins?.size) { + return true + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems[newItemPosition] + if (oldItem.origins.size != newItem.origins.size) { return false } if (oldItem.coverUrl != newItem.coverUrl) { @@ -56,20 +58,20 @@ class DiffCallBack(private val oldItems: List, private val newItems: if (oldItem.author != newItem.author) { payload.putString("author", newItem.author) } - if (oldItem.origins?.size != newItem.origins?.size) { - payload.putInt("origins", newItem.origins?.size ?: 1) + if (oldItem.origins.size != newItem.origins.size) { + payload.putInt("origins", newItem.origins.size) } if (oldItem.coverUrl != newItem.coverUrl) { - payload.putString("group", newItem.coverUrl) + payload.putString("cover", newItem.coverUrl) } if (oldItem.kind != newItem.kind) { - payload.putString("enabled", newItem.kind) + payload.putString("kind", newItem.kind) } if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { - payload.putString("enabled", newItem.latestChapterTitle) + payload.putString("last", newItem.latestChapterTitle) } if (oldItem.intro != newItem.intro) { - payload.putString("enabled", newItem.intro) + payload.putString("intro", newItem.intro) } return payload } diff --git a/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt b/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt index cfe7699a8..e59605495 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt @@ -6,7 +6,7 @@ import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.SearchKeyword import io.legado.app.ui.widget.anima.explosion_field.ExplosionField -import kotlinx.android.synthetic.main.item_text.view.* +import kotlinx.android.synthetic.main.item_fillet_text.view.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -15,7 +15,7 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick class HistoryKeyAdapter(activity: SearchActivity, val callBack: CallBack) : - SimpleRecyclerAdapter(activity, R.layout.item_text) { + SimpleRecyclerAdapter(activity, R.layout.item_fillet_text) { private val explosionField = ExplosionField.attach2Window(activity) diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt index fbdcb99cf..a96244113 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt @@ -40,7 +40,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) : with(itemView) { tv_name.text = searchBook.name tv_author.text = context.getString(R.string.author_show, searchBook.author) - bv_originCount.setBadgeCount(searchBook.origins?.size ?: 1) + bv_originCount.setBadgeCount(searchBook.origins.size) upLasted(itemView, searchBook.latestChapterTitle) tv_introduce.text = context.getString(R.string.intro_show, searchBook.intro) upKind(itemView, searchBook.getKindList()) @@ -56,9 +56,9 @@ class SearchAdapter(context: Context, val callBack: CallBack) : "name" -> tv_name.text = searchBook.name "author" -> tv_author.text = context.getString(R.string.author_show, searchBook.author) - "originCount" -> bv_originCount.setBadgeCount(searchBook.origins?.size ?: 1) - "lasted" -> upLasted(itemView, searchBook.latestChapterTitle) - "introduce" -> tv_introduce.text = + "origins" -> bv_originCount.setBadgeCount(searchBook.origins.size) + "last" -> upLasted(itemView, searchBook.latestChapterTitle) + "intro" -> tv_introduce.text = context.getString(R.string.intro_show, searchBook.intro) "kind" -> upKind(itemView, searchBook.getKindList()) "cover" -> iv_cover.load( diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index df104de88..47671626d 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -85,8 +85,8 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { private fun precisionSearch(searchBooks: List) { val books = arrayListOf() searchBooks.forEach { searchBook -> - if (searchBook.name.equals(searchKey, true) - || searchBook.author.equals(searchKey, true) + if (searchBook.name.contains(searchKey, true) + || searchBook.author.contains(searchKey, true) ) books.add(searchBook) } App.db.searchBookDao().insert(*books.toTypedArray()) @@ -130,9 +130,9 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { } } } else if (searchKey == item.author) { - for ((i, searchBook) in copyDataS.withIndex()) { + for ((index, searchBook) in copyDataS.withIndex()) { if (searchKey != searchBook.name && searchKey == searchBook.author) { - copyDataS.add(i, item) + copyDataS.add(index, item) break } } @@ -141,6 +141,31 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { } } } + searchBooks.sortWith(Comparator { o1, o2 -> + if (o1.name == searchKey && o2.name != searchKey) { + 1 + } else if (o1.name != searchKey && o2.name == searchKey) { + -1 + } else if (o1.author == searchKey && o2.author != searchKey) { + 1 + } else if (o1.author != searchKey && o2.author == searchKey) { + -1 + } else if (o1.name == o2.name) { + when { + o1.origins.size > o2.origins.size -> { + 1 + } + o1.origins.size < o2.origins.size -> { + -1 + } + else -> { + 0 + } + } + } else { + 0 + } + }) searchBooks = copyDataS searchBookLiveData.postValue(copyDataS) } diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index 082faa675..c0eab1b32 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -25,9 +25,13 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.widget.KeyboardToolPop -import io.legado.app.utils.* +import io.legado.app.utils.GSON +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModel +import io.legado.app.utils.shareWithQr import kotlinx.android.synthetic.main.activity_book_source_edit.* import org.jetbrains.anko.displayMetrics +import org.jetbrains.anko.share import org.jetbrains.anko.startActivity import org.jetbrains.anko.toast import kotlin.math.abs @@ -84,9 +88,7 @@ class BookSourceEditActivity : } } R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } - R.id.menu_share_str -> GSON.toJson(getSource())?.let { sourceStr -> - shareText(getString(R.string.share_book_source), sourceStr) - } + R.id.menu_share_str -> GSON.toJson(getSource())?.let { share(it) } R.id.menu_share_qr -> GSON.toJson(getSource())?.let { sourceStr -> shareWithQr(getString(R.string.share_book_source), sourceStr) } @@ -353,18 +355,16 @@ class BookSourceEditActivity : } private fun showKeyboardTopPopupWindow() { - mSoftKeyboardTool?.isShowing?.let { if (it) return } - if (!isFinishing) { - mSoftKeyboardTool?.showAtLocation(ll_content, Gravity.BOTTOM, 0, 0) + mSoftKeyboardTool?.let { + if (it.isShowing) return + if (!isFinishing) { + it.showAtLocation(ll_content, Gravity.BOTTOM, 0, 0) + } } } private fun closePopupWindow() { - mSoftKeyboardTool?.let { - if (it.isShowing) { - it.dismiss() - } - } + mSoftKeyboardTool?.dismiss() } private inner class KeyboardOnGlobalChangeListener : ViewTreeObserver.OnGlobalLayoutListener { diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index 5b375b4cf..c94b59d7c 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -30,6 +30,7 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_source.* @@ -109,7 +110,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity private fun initRecyclerView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) adapter = BookSourceAdapter(this, this) recycler_view.adapter = adapter val itemTouchCallback = ItemTouchCallback() diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt index 314b45d08..d4a466948 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt @@ -22,7 +22,11 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton -import io.legado.app.utils.* +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModelOfActivity +import io.legado.app.utils.requestInputMethod +import io.legado.app.utils.splitNotBlank import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_group_manage.view.* @@ -60,7 +64,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tool_bar.setOnMenuItemClickListener(this) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { val groups = linkedSetOf() diff --git a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt index 4b129acea..d1ce27f2e 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt @@ -18,8 +18,8 @@ import io.legado.app.R import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.SearchBook +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.getVerticalDivider import io.legado.app.utils.getViewModel import io.legado.app.utils.putPrefBoolean import kotlinx.android.synthetic.main.dialog_change_source.* @@ -92,7 +92,7 @@ class ChangeSourceDialog : DialogFragment(), private fun initRecyclerView() { adapter = ChangeSourceAdapter(requireContext(), this) recycler_view.layoutManager = LinearLayoutManager(context) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { diff --git a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt index 13b145a1e..1a683427f 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt @@ -58,7 +58,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio App.db.searchBookDao().insert(searchBook) if (screenKey.isEmpty()) { searchBooks.add(searchBook) - } else if (searchBook.originName.contains(screenKey)) { + } else if (searchBook.name.contains(screenKey)) { searchBooks.add(searchBook) } upAdapter() diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt b/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt index c32c8bc42..b2337d0d9 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt +++ b/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt @@ -14,7 +14,7 @@ import io.legado.app.R import io.legado.app.base.VMBaseFragment import io.legado.app.data.entities.Bookmark import io.legado.app.lib.theme.ATH -import io.legado.app.utils.getVerticalDivider +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.getViewModelOfActivity import kotlinx.android.synthetic.main.fragment_bookmark.* @@ -38,7 +38,7 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_ ATH.applyEdgeEffectColor(recycler_view) adapter = BookmarkAdapter(this) recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter } diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt b/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt index 0d9234e36..0bfc9a765 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt +++ b/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt @@ -15,7 +15,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp import io.legado.app.lib.theme.backgroundColor import io.legado.app.ui.widget.recycler.UpLinearLayoutManager -import io.legado.app.utils.getVerticalDivider +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.observeEvent import kotlinx.android.synthetic.main.fragment_chapter_list.* @@ -48,7 +48,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme adapter = ChapterListAdapter(requireContext(), this) mLayoutManager = UpLinearLayoutManager(requireContext()) recycler_view.layoutManager = mLayoutManager - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter } diff --git a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt index 16fd3941b..f314b5f1b 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.Dispatchers.Main import org.jetbrains.anko.toast object BackupRestoreUi { - + private const val selectFolderRequestCode = 21 private const val backupSelectRequestCode = 22 private const val restoreSelectRequestCode = 33 private const val oldDataRequestCode = 11 @@ -34,7 +34,7 @@ object BackupRestoreUi { fun backup(fragment: Fragment) { val backupPath = AppConfig.backupPath if (backupPath.isNullOrEmpty()) { - selectBackupFolder(fragment) + selectBackupFolder(fragment, backupSelectRequestCode) } else { if (backupPath.isContentPath()) { val uri = Uri.parse(backupPath) @@ -46,40 +46,49 @@ object BackupRestoreUi { fragment.toast(R.string.backup_success) } } else { - selectBackupFolder(fragment) + selectBackupFolder(fragment, backupSelectRequestCode) } } else { - backupUsePermission(fragment) + backupUsePermission(fragment, requestCode = backupSelectRequestCode) } } } - private fun backupUsePermission(fragment: Fragment, path: String = Backup.legadoPath) { + private fun backupUsePermission( + fragment: Fragment, + path: String = Backup.legadoPath, + requestCode: Int = selectFolderRequestCode + ) { PermissionsCompat.Builder(fragment) .addPermissions(*Permissions.Group.STORAGE) .rationale(R.string.tip_perm_request_storage) .onGranted { - Coroutine.async { - AppConfig.backupPath = Backup.legadoPath - Backup.backup(fragment.requireContext(), path) - }.onSuccess { - fragment.toast(R.string.backup_success) + when (requestCode) { + selectFolderRequestCode -> AppConfig.backupPath = Backup.legadoPath + else -> { + Coroutine.async { + AppConfig.backupPath = Backup.legadoPath + Backup.backup(fragment.requireContext(), path) + }.onSuccess { + fragment.toast(R.string.backup_success) + } + } } } .request() } - fun selectBackupFolder(fragment: Fragment) { + fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) { fragment.alert { titleResource = R.string.select_folder items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index -> when (index) { - 0 -> backupUsePermission(fragment) + 0 -> backupUsePermission(fragment, requestCode = requestCode) 1 -> { try { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - fragment.startActivityForResult(intent, backupSelectRequestCode) + fragment.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { e.printStackTrace() fragment.toast(e.localizedMessage ?: "ERROR") @@ -88,7 +97,7 @@ object BackupRestoreUi { 2 -> { FileChooserDialog.show( fragment.childFragmentManager, - backupSelectRequestCode, + requestCode, mode = FileChooserDialog.DIRECTORY ) } @@ -231,6 +240,9 @@ object BackupRestoreUi { App.INSTANCE.toast(R.string.restore_success) } } + selectFolderRequestCode -> { + AppConfig.backupPath = currentPath + } } } @@ -240,7 +252,8 @@ object BackupRestoreUi { data?.data?.let { uri -> App.INSTANCE.contentResolver.takePersistableUriPermission( uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) AppConfig.backupPath = uri.toString() Coroutine.async { @@ -254,7 +267,8 @@ object BackupRestoreUi { data?.data?.let { uri -> App.INSTANCE.contentResolver.takePersistableUriPermission( uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) AppConfig.backupPath = uri.toString() Coroutine.async { @@ -264,6 +278,16 @@ object BackupRestoreUi { } } } + selectFolderRequestCode -> if (resultCode == RESULT_OK) { + data?.data?.let { uri -> + App.INSTANCE.contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + AppConfig.backupPath = uri.toString() + } + } oldDataRequestCode -> if (resultCode == RESULT_OK) data?.data?.let { uri -> ImportOldData.importUri(uri) diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index ed5e8cf9a..1a1851ca1 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -141,6 +141,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar AppConfig.isNightTheme = true } } + putPrefInt("defaultTheme", which) App.INSTANCE.applyDayNight() recreateActivities() } diff --git a/app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt b/app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt index 8cc5d7c7e..08a96a340 100644 --- a/app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt +++ b/app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt @@ -10,7 +10,7 @@ import io.legado.app.data.entities.Book import io.legado.app.data.entities.SearchBook import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.widget.recycler.LoadMoreView -import io.legado.app.utils.getVerticalDivider +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_explore_show.* import org.jetbrains.anko.startActivity @@ -34,7 +34,7 @@ class ExploreShowActivity : VMBaseActivity(R.layout.activi private fun initRecyclerView() { adapter = ExploreShowAdapter(this, this) recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) recycler_view.adapter = adapter loadMoreView = LoadMoreView(this) adapter.addFooterView(loadMoreView) diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt b/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt index 46c1271fc..af8cf50f4 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt @@ -15,7 +15,11 @@ import io.legado.app.R import io.legado.app.constant.Theme import io.legado.app.ui.filechooser.adapter.FileAdapter import io.legado.app.ui.filechooser.adapter.PathAdapter -import io.legado.app.utils.* +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.FileUtils +import io.legado.app.utils.applyTint +import io.legado.app.utils.gone +import io.legado.app.utils.visible import kotlinx.android.synthetic.main.dialog_file_chooser.* @@ -133,7 +137,7 @@ class FileChooserDialog : DialogFragment(), fileAdapter = FileAdapter(requireContext(), this) pathAdapter = PathAdapter(requireContext(), this) - rv_file.addItemDecoration(rv_file.getVerticalDivider()) + rv_file.addItemDecoration(VerticalDivider(requireContext())) rv_file.layoutManager = LinearLayoutManager(activity) rv_file.adapter = fileAdapter diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index 16b7b286f..f49cf2ea6 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf import android.annotation.SuppressLint import android.os.Bundle +import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View @@ -17,7 +18,10 @@ import io.legado.app.constant.AppConst import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.BookGroup -import io.legado.app.lib.dialogs.* +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.customView +import io.legado.app.lib.dialogs.noButton +import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor import io.legado.app.ui.book.arrange.ArrangeBookActivity @@ -27,6 +31,7 @@ import io.legado.app.ui.download.DownloadActivity import io.legado.app.ui.importbook.ImportBookActivity import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* +import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.fragment_bookshelf.* import kotlinx.android.synthetic.main.view_tab_layout.* @@ -61,7 +66,7 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b super.onCompatOptionsItemSelected(item) when (item.itemId) { R.id.menu_search -> startActivity() - R.id.menu_bookshelf_layout -> selectBookshelfLayout() + R.id.menu_bookshelf_layout -> configBookshelf() R.id.menu_group_manage -> GroupManageDialog() .show(childFragmentManager, "groupManageDialog") R.id.menu_add_local -> startActivity() @@ -145,14 +150,35 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } } - private fun selectBookshelfLayout() { - selector( - title = "选择书架布局", - items = resources.getStringArray(R.array.bookshelf_layout).toList() - ) { _, index -> - putPrefInt(PreferKey.bookshelfLayout, index) - activity?.recreate() - } + @SuppressLint("InflateParams") + private fun configBookshelf() { + requireContext().alert(titleResource = R.string.bookshelf_layout) { + val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout) + val bookshelfSort = getPrefInt(PreferKey.bookshelfSort) + val root = LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_bookshelf_config, null).apply { + rg_layout.checkByIndex(bookshelfLayout) + rg_sort.checkByIndex(bookshelfSort) + } + customView = root + okButton { + root.apply { + var changed = false + if (bookshelfLayout != rg_layout.getCheckedIndex()) { + putPrefInt(PreferKey.bookshelfLayout, rg_layout.getCheckedIndex()) + changed = true + } + if (bookshelfSort != rg_sort.getCheckedIndex()) { + putPrefInt(PreferKey.bookshelfSort, rg_sort.getCheckedIndex()) + changed = true + } + if (changed) { + activity?.recreate() + } + } + } + noButton() + }.show().applyTint() } @SuppressLint("InflateParams") diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt index 1a462a294..49cadb56c 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt @@ -103,10 +103,16 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), -3 -> App.db.bookDao().observeAudio() else -> App.db.bookDao().observeByGroup(groupId) } - bookshelfLiveData?.observe(this, Observer { + bookshelfLiveData?.observe(this, Observer { list -> + val books = when (getPrefInt(PreferKey.bookshelfSort)) { + 1 -> list.sortedByDescending { it.latestChapterTime } + 2 -> list.sortedBy { it.name } + 3 -> list.sortedBy { it.order } + else -> list.sortedByDescending { it.durChapterTime } + } val diffResult = DiffUtil - .calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), it)) - booksAdapter.setItems(it, diffResult) + .calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books)) + booksAdapter.setItems(books, diffResult) }) } diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt index 6c4691284..3ceeca8b6 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt @@ -14,8 +14,8 @@ import io.legado.app.lib.theme.accentColor import io.legado.app.utils.ACache import io.legado.app.utils.gone import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.item_fillet_text.view.* import kotlinx.android.synthetic.main.item_find_book.view.* -import kotlinx.android.synthetic.main.item_text.view.* import kotlinx.coroutines.CoroutineScope import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick @@ -46,7 +46,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca gl_child.removeAllViews() kindList.map { kind -> val tv = LayoutInflater.from(context) - .inflate(R.layout.item_text, gl_child, false) + .inflate(R.layout.item_fillet_text, gl_child, false) gl_child.addView(tv) tv.text_view.text = kind.title tv.text_view.onClick { diff --git a/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt index bfd4d23a1..6ea1d85bd 100644 --- a/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt +++ b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.qrcode import android.app.Activity import android.content.Intent +import android.graphics.BitmapFactory import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -11,7 +12,7 @@ import io.legado.app.R import io.legado.app.base.BaseActivity import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat -import io.legado.app.utils.RealPathUtil +import io.legado.app.utils.readBytes import kotlinx.android.synthetic.main.activity_qrcode_capture.* import kotlinx.android.synthetic.main.view_title_bar.* import org.jetbrains.anko.toast @@ -98,9 +99,11 @@ class QrCodeActivity : BaseActivity(R.layout.activity_qrcode_capture), QRCodeVie zxingview.startSpotAndShowRect() // 显示扫描框,并开始识别 if (resultCode == Activity.RESULT_OK && requestCode == requestQrImage) { - val picturePath = RealPathUtil.getPath(this, it) // 本来就用到 QRCodeView 时可直接调 QRCodeView 的方法,走通用的回调 - zxingview.decodeQRCode(picturePath) + it.readBytes(this)?.let { bytes -> + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + zxingview.decodeQRCode(bitmap) + } } } } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt index 6f9a61f4c..df39b9a1d 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt @@ -22,7 +22,11 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton -import io.legado.app.utils.* +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModelOfActivity +import io.legado.app.utils.requestInputMethod +import io.legado.app.utils.splitNotBlank import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_group_manage.view.* @@ -60,7 +64,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tool_bar.setOnMenuItemClickListener(this) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter App.db.replaceRuleDao().liveGroup().observe(viewLifecycleOwner, Observer { val groups = linkedSetOf() diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 4bcaa7945..5044e2667 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -28,6 +28,7 @@ import io.legado.app.lib.theme.primaryTextColor import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_replace_rule.* @@ -54,6 +55,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi private var dataInit = false override fun onActivityCreated(savedInstanceState: Bundle?) { + initUriScheme() initRecyclerView() initSearchView() initSelectActionView() @@ -72,12 +74,28 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi return super.onPrepareOptionsMenu(menu) } + private fun initUriScheme() { + intent.data?.let { + when (it.path) { + "/importonline" -> it.getQueryParameter("src")?.let { url -> + Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() + viewModel.importSource(url) { msg -> + title_bar.snackbar(msg) + } + } + else -> { + toast("格式不对") + } + } + } + } + private fun initRecyclerView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) adapter = ReplaceRuleAdapter(this, this) recycler_view.adapter = adapter - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) val itemTouchCallback = ItemTouchCallback() itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.isCanDrag = true @@ -306,9 +324,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } override fun edit(rule: ReplaceRule) { - ReplaceEditDialog - .newInstance(rule.id) - .show(supportFragmentManager, "editReplace") + ReplaceEditDialog.show(supportFragmentManager, rule.id) } override fun toTop(rule: ReplaceRule) { diff --git a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt index 5470b4c5f..de5002931 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.appcompat.widget.Toolbar import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.constant.Theme @@ -22,14 +23,13 @@ class ReplaceEditDialog : DialogFragment(), companion object { - fun newInstance(id: Long? = null): ReplaceEditDialog { + fun show(fragmentManager: FragmentManager, id: Long = -1, pattern: String? = null) { val dialog = ReplaceEditDialog() - id?.let { - val bundle = Bundle() - bundle.putLong("id", id) - dialog.arguments = bundle - } - return dialog + val bundle = Bundle() + bundle.putLong("id", id) + bundle.putString("pattern", pattern) + dialog.arguments = bundle + dialog.show(fragmentManager, "editReplace") } } @@ -56,7 +56,7 @@ class ReplaceEditDialog : DialogFragment(), tool_bar.inflateMenu(R.menu.replace_edit) tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.setOnMenuItemClickListener(this) - viewModel.replaceRuleData.observe(this, Observer { + viewModel.replaceRuleData.observe(viewLifecycleOwner, Observer { upReplaceView(it) }) arguments?.let { diff --git a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt index 281a0ac2d..f326c88c3 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt @@ -19,6 +19,12 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application App.db.replaceRuleDao().findById(id)?.let { replaceRuleData.postValue(it) } + } else { + bundle.getString("pattern")?.let { pattern -> + replaceRuleData.postValue( + ReplaceRule(pattern = pattern) + ) + } } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt index 5476657a4..3b53a6475 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt @@ -15,7 +15,7 @@ import io.legado.app.lib.theme.ATH import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.widget.recycler.LoadMoreView -import io.legado.app.utils.getVerticalDivider +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_rss_artivles.* import kotlinx.android.synthetic.main.view_load_more.view.* @@ -70,7 +70,7 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi private fun initView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) adapter = RssArticlesAdapter(this, this) recycler_view.adapter = adapter loadMoreView = LoadMoreView(this) diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt index a8eb39cea..f50329296 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt @@ -10,7 +10,7 @@ import io.legado.app.base.BaseActivity import io.legado.app.data.entities.RssStar import io.legado.app.lib.theme.ATH import io.legado.app.ui.rss.read.ReadRssActivity -import io.legado.app.utils.getVerticalDivider +import io.legado.app.ui.widget.recycler.VerticalDivider import kotlinx.android.synthetic.main.view_refresh_recycler.* import org.jetbrains.anko.startActivity @@ -29,7 +29,7 @@ class RssFavoritesActivity : BaseActivity(R.layout.activity_rss_favorites), private fun initView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) adapter = RssFavoritesAdapter(this, this) recycler_view.adapter = adapter } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index c68db66a1..255505178 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -5,7 +5,9 @@ import android.os.Bundle import android.view.KeyEvent import android.view.Menu import android.view.MenuItem +import android.webkit.WebResourceRequest import android.webkit.WebSettings +import android.webkit.WebView import android.webkit.WebViewClient import androidx.lifecycle.Observer import io.legado.app.R @@ -14,10 +16,11 @@ import io.legado.app.lib.theme.DrawableUtils import io.legado.app.lib.theme.primaryTextColor import io.legado.app.utils.NetworkUtils import io.legado.app.utils.getViewModel -import io.legado.app.utils.shareText +import io.legado.app.utils.openUrl import kotlinx.android.synthetic.main.activity_rss_read.* import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils +import org.jetbrains.anko.share import org.jsoup.Jsoup import org.jsoup.safety.Whitelist @@ -54,7 +57,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r when (item.itemId) { R.id.menu_rss_star -> viewModel.favorite() R.id.menu_share_it -> viewModel.rssArticle?.let { - shareText("链接分享", it.link) + share(it.link) } R.id.menu_aloud -> readAloud() } @@ -62,10 +65,24 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } private fun initWebView() { - webView.webViewClient = WebViewClient() + webView.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest? + ): Boolean { + if (request?.url?.scheme == "http" || request?.url?.scheme == "https") { + return false + } + request?.url?.let { + openUrl(it) + } + return true + } + } webView.settings.apply { mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW domStorageEnabled = true + allowContentAccess = true } } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt index 09d5d6b2f..944135b45 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.content.Intent import android.graphics.Rect import android.os.Bundle import android.view.Gravity @@ -19,22 +20,25 @@ import io.legado.app.constant.AppConst import io.legado.app.data.entities.RssSource import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH +import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.rss.source.debug.RssSourceDebugActivity import io.legado.app.ui.widget.KeyboardToolPop -import io.legado.app.utils.* +import io.legado.app.utils.GSON +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModel +import io.legado.app.utils.shareWithQr import kotlinx.android.synthetic.main.activity_rss_source_edit.* -import org.jetbrains.anko.displayMetrics -import org.jetbrains.anko.startActivity -import org.jetbrains.anko.toast +import org.jetbrains.anko.* import kotlin.math.abs class RssSourceEditActivity : VMBaseActivity(R.layout.activity_rss_source_edit, false), + ViewTreeObserver.OnGlobalLayoutListener, KeyboardToolPop.CallBack { private var mSoftKeyboardTool: PopupWindow? = null private var mIsSoftKeyBoardShowing = false - + private val qrRequestCode = 101 private val adapter = RssSourceEditAdapter() private val sourceEntities: ArrayList = ArrayList() @@ -99,9 +103,10 @@ class RssSourceEditActivity : clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr)) } } + R.id.menu_qr_code_camera -> startActivityForResult(qrRequestCode) R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } R.id.menu_share_str -> GSON.toJson(getRssSource())?.let { sourceStr -> - shareText(getString(R.string.share_rss_source), sourceStr) + share(sourceStr) } R.id.menu_share_qr -> GSON.toJson(getRssSource())?.let { sourceStr -> shareWithQr(getString(R.string.share_rss_source), sourceStr) @@ -113,7 +118,7 @@ class RssSourceEditActivity : private fun initView() { ATH.applyEdgeEffectColor(recycler_view) mSoftKeyboardTool = KeyboardToolPop(this, AppConst.keyboardToolChars, this) - window.decorView.viewTreeObserver.addOnGlobalLayoutListener(KeyboardOnGlobalChangeListener()) + window.decorView.viewTreeObserver.addOnGlobalLayoutListener(this) recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.adapter = adapter } @@ -192,37 +197,46 @@ class RssSourceEditActivity : } private fun showKeyboardTopPopupWindow() { - mSoftKeyboardTool?.isShowing?.let { if (it) return } - if (!isFinishing) { - mSoftKeyboardTool?.showAtLocation(ll_content, Gravity.BOTTOM, 0, 0) + mSoftKeyboardTool?.let { + if (it.isShowing) return + if (!isFinishing) { + it.showAtLocation(ll_content, Gravity.BOTTOM, 0, 0) + } } } private fun closePopupWindow() { - mSoftKeyboardTool?.let { - if (it.isShowing) { - it.dismiss() + mSoftKeyboardTool?.dismiss() + } + + override fun onGlobalLayout() { + val rect = Rect() + // 获取当前页面窗口的显示范围 + window.decorView.getWindowVisibleDisplayFrame(rect) + val screenHeight = this@RssSourceEditActivity.displayMetrics.heightPixels + val keyboardHeight = screenHeight - rect.bottom // 输入法的高度 + val preShowing = mIsSoftKeyBoardShowing + if (abs(keyboardHeight) > screenHeight / 5) { + mIsSoftKeyBoardShowing = true // 超过屏幕五分之一则表示弹出了输入法 + recycler_view.setPadding(0, 0, 0, 100) + showKeyboardTopPopupWindow() + } else { + mIsSoftKeyBoardShowing = false + recycler_view.setPadding(0, 0, 0, 0) + if (preShowing) { + closePopupWindow() } } } - private inner class KeyboardOnGlobalChangeListener : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - val rect = Rect() - // 获取当前页面窗口的显示范围 - window.decorView.getWindowVisibleDisplayFrame(rect) - val screenHeight = this@RssSourceEditActivity.displayMetrics.heightPixels - val keyboardHeight = screenHeight - rect.bottom // 输入法的高度 - val preShowing = mIsSoftKeyBoardShowing - if (abs(keyboardHeight) > screenHeight / 5) { - mIsSoftKeyBoardShowing = true // 超过屏幕五分之一则表示弹出了输入法 - recycler_view.setPadding(0, 0, 0, 100) - showKeyboardTopPopupWindow() - } else { - mIsSoftKeyBoardShowing = false - recycler_view.setPadding(0, 0, 0, 0) - if (preShowing) { - closePopupWindow() + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + qrRequestCode -> if (resultCode == RESULT_OK) { + data?.getStringExtra("result")?.let { + viewModel.importSource(it) { source: RssSource -> + upRecyclerView(source) + } } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt index ae6e8c739..eb9c4e034 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt @@ -71,4 +71,16 @@ class RssSourceEditViewModel(application: Application) : BaseViewModel(applicati } } } + + fun importSource(text: String, finally: (source: RssSource) -> Unit) { + execute { + val text1 = text.trim() + GSON.fromJsonObject(text1)?.let { + finally.invoke(it) + } + }.onError { + toast(it.localizedMessage ?: "Error") + } + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt index 1ba2a567b..391281a4e 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt @@ -22,7 +22,11 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton -import io.legado.app.utils.* +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModelOfActivity +import io.legado.app.utils.requestInputMethod +import io.legado.app.utils.splitNotBlank import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_group_manage.view.* @@ -60,7 +64,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tool_bar.setOnMenuItemClickListener(this) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter App.db.rssSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { val groups = linkedSetOf() diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt index e83a15c19..22277476e 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt @@ -29,6 +29,7 @@ import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_rss_source.* @@ -49,13 +50,14 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r get() = getViewModel(RssSourceViewModel::class.java) private val importRecordKey = "rssSourceRecordKey" private val qrRequestCode = 101 - private val importSource = 13141 + private val importSource = 124 private lateinit var adapter: RssSourceAdapter private var sourceLiveData: LiveData>? = null private var groups = hashSetOf() private var groupMenu: SubMenu? = null override fun onActivityCreated(savedInstanceState: Bundle?) { + initUriScheme() initRecyclerView() initSearchView() initLiveDataGroup() @@ -101,10 +103,26 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r return true } + private fun initUriScheme() { + intent.data?.let { + when (it.path) { + "/importonline" -> it.getQueryParameter("src")?.let { url -> + Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() + viewModel.importSource(url) { msg -> + title_bar.snackbar(msg) + } + } + else -> { + toast("格式不对") + } + } + } + } + private fun initRecyclerView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) + recycler_view.addItemDecoration(VerticalDivider(this)) adapter = RssSourceAdapter(this, this) recycler_view.adapter = adapter val itemTouchCallback = ItemTouchCallback() diff --git a/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt b/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt index a959ce515..504837afd 100644 --- a/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt @@ -34,11 +34,11 @@ class DetailSeekBar(context: Context, attrs: AttributeSet?) : FrameLayout(contex seek_bar.max = typedArray.getInteger(R.styleable.DetailSeekBar_max, 0) typedArray.recycle() - iv_seek_add.onClick { + iv_seek_plus.onClick { seek_bar.progressAdd(1) onChanged?.invoke(seek_bar.progress) } - iv_seek_remove.onClick { + iv_seek_reduce.onClick { seek_bar.progressAdd(-1) onChanged?.invoke(seek_bar.progress) } diff --git a/app/src/main/java/io/legado/app/ui/widget/KeyboardToolPop.kt b/app/src/main/java/io/legado/app/ui/widget/KeyboardToolPop.kt index 01d36ca4a..56dff107c 100644 --- a/app/src/main/java/io/legado/app/ui/widget/KeyboardToolPop.kt +++ b/app/src/main/java/io/legado/app/ui/widget/KeyboardToolPop.kt @@ -10,7 +10,7 @@ import androidx.recyclerview.widget.RecyclerView import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import kotlinx.android.synthetic.main.item_text.view.* +import kotlinx.android.synthetic.main.item_fillet_text.view.* import kotlinx.android.synthetic.main.popup_keyboard_tool.view.* import org.jetbrains.anko.sdk27.listeners.onClick @@ -23,7 +23,7 @@ class KeyboardToolPop( init { @SuppressLint("InflateParams") - this.contentView = LayoutInflater.from(context).inflate(R.layout.popup_keyboard_tool, null) + contentView = LayoutInflater.from(context).inflate(R.layout.popup_keyboard_tool, null) isTouchable = true isOutsideTouchable = false @@ -40,7 +40,7 @@ class KeyboardToolPop( } inner class Adapter(context: Context) : - SimpleRecyclerAdapter(context, R.layout.item_text) { + SimpleRecyclerAdapter(context, R.layout.item_fillet_text) { override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList) { with(holder.itemView) { diff --git a/app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt b/app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt index ab933edbb..2d830ca55 100644 --- a/app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt @@ -12,6 +12,7 @@ class LabelsBar(context: Context, attrs: AttributeSet?) : LinearLayout(context, private val unUsedViews = arrayListOf() private val usedViews = arrayListOf() + var textSize = 12f fun setLabels(labels: Array) { clear() @@ -51,6 +52,7 @@ class LabelsBar(context: Context, attrs: AttributeSet?) : LinearLayout(context, unUsedViews.removeAt(unUsedViews.lastIndex) } } + tv.textSize = textSize tv.text = label addView(tv) } diff --git a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt index ab931f3ab..d19c8929f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt @@ -12,8 +12,8 @@ import com.google.android.material.appbar.AppBarLayout import io.legado.app.R import io.legado.app.lib.theme.primaryColor import io.legado.app.utils.activity -import io.legado.app.utils.getNavigationBarHeight -import io.legado.app.utils.getStatusBarHeight +import io.legado.app.utils.navigationBarHeight +import io.legado.app.utils.statusBarHeight import org.jetbrains.anko.backgroundColor import org.jetbrains.anko.bottomPadding import org.jetbrains.anko.topPadding @@ -132,11 +132,11 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a } if (a.getBoolean(R.styleable.TitleBar_fitStatusBar, true)) { - topPadding = context.getStatusBarHeight() + topPadding = context.statusBarHeight } if (a.getBoolean(R.styleable.TitleBar_fitNavigationBar, false)) { - bottomPadding = context.getNavigationBarHeight() + bottomPadding = context.navigationBarHeight } backgroundColor = context.primaryColor diff --git a/app/src/main/java/io/legado/app/ui/widget/checkbox/SmoothCheckBox.kt b/app/src/main/java/io/legado/app/ui/widget/checkbox/SmoothCheckBox.kt index 7179d8a38..b18e59332 100644 --- a/app/src/main/java/io/legado/app/ui/widget/checkbox/SmoothCheckBox.kt +++ b/app/src/main/java/io/legado/app/ui/widget/checkbox/SmoothCheckBox.kt @@ -40,12 +40,12 @@ class SmoothCheckBox @JvmOverloads constructor( private var mFloorUnCheckedColor = 0 private var mChecked = false private var mTickDrawing = false - private var mListener: OnCheckedChangeListener? = null + var onCheckedChangeListener: ((checkBox: SmoothCheckBox, isChecked: Boolean) -> Unit)? = null init { val ta = context.obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox) var tickColor = ThemeStore.accentColor(context) - mCheckedColor = context.getCompatColor(R.color.background_card) + mCheckedColor = context.getCompatColor(R.color.background_menu) mUnCheckedColor = context.getCompatColor(R.color.background_menu) mFloorColor = context.getCompatColor(R.color.transparent30) tickColor = ta.getColor(R.styleable.SmoothCheckBox_color_tick, tickColor) @@ -89,9 +89,7 @@ class SmoothCheckBox @JvmOverloads constructor( mChecked = checked reset() invalidate() - if (mListener != null) { - mListener!!.onCheckedChanged(this@SmoothCheckBox, mChecked) - } + onCheckedChangeListener?.invoke(this@SmoothCheckBox, mChecked) } override fun toggle() { @@ -114,9 +112,7 @@ class SmoothCheckBox @JvmOverloads constructor( } else { startUnCheckedAnimation() } - if (mListener != null) { - mListener!!.onCheckedChanged(this@SmoothCheckBox, mChecked) - } + onCheckedChangeListener?.invoke(this@SmoothCheckBox, mChecked) } else { this.isChecked = checked } @@ -308,14 +304,6 @@ class SmoothCheckBox @JvmOverloads constructor( }, mAnimDuration.toLong()) } - fun setOnCheckedChangeListener(l: OnCheckedChangeListener?) { - mListener = l - } - - interface OnCheckedChangeListener { - fun onCheckedChanged(checkBox: SmoothCheckBox?, isChecked: Boolean) - } - companion object { private const val DEF_DRAW_SIZE = 25 private const val DEF_ANIM_DURATION = 300 diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt index f7c73030a..f2724abcd 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt @@ -20,7 +20,7 @@ class FontAdapter(context: Context, val callBack: CallBack) : tv_font.typeface = typeface tv_font.text = item.name this.onClick { callBack.onClick(item) } - if (item.name == callBack.curFilePath().substringAfterLast(File.separator)) { + if (item.name == callBack.curFilePath.substringAfterLast(File.separator)) { iv_checked.visible() } else { iv_checked.invisible() @@ -38,6 +38,6 @@ class FontAdapter(context: Context, val callBack: CallBack) : interface CallBack { fun onClick(file: File) - fun curFilePath(): String + val curFilePath: String } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index be5523b58..87d87e8d6 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -16,7 +16,9 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseDialogFragment +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey +import io.legado.app.help.AppConfig import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat import io.legado.app.lib.dialogs.alert @@ -87,13 +89,14 @@ class FontSelectDialog : BaseDialogFragment(), override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_default -> { - val cb = (parentFragment as? CallBack) ?: (activity as? CallBack) - cb?.let { - if (it.curFontPath != "") { - it.selectFile("") + val requireContext = requireContext() + requireContext.alert(titleResource = R.string.system_typeface) { + items(requireContext.resources.getStringArray(R.array.system_typefaces).toList()) { _, i -> + AppConfig.systemTypefaces = i + onDefaultFontChange() + dismiss() } - } - dismiss() + }.show() } R.id.menu_other -> { openFolder() @@ -103,46 +106,48 @@ class FontSelectDialog : BaseDialogFragment(), } private fun openFolder() { - alert { - titleResource = R.string.select_folder - items(resources.getStringArray(R.array.select_folder).toList()) { _, index -> - when (index) { - 0 -> { - val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts" - putPrefString(PreferKey.fontFolder, path) - getFontFilesByPermission(path) - } - 1 -> { - try { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivityForResult(intent, fontFolderRequestCode) - } catch (e: java.lang.Exception) { - e.printStackTrace() - requireContext().toast(e.localizedMessage ?: "ERROR") + launch(Main) { + alert { + titleResource = R.string.select_folder + items(resources.getStringArray(R.array.select_folder).toList()) { _, index -> + when (index) { + 0 -> { + val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts" + putPrefString(PreferKey.fontFolder, path) + getFontFilesByPermission(path) } - } - 2 -> { - PermissionsCompat.Builder(this@FontSelectDialog) - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { - FileChooserDialog.show( - childFragmentManager, - fontFolderRequestCode, - mode = FileChooserDialog.DIRECTORY - ) + 1 -> { + try { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivityForResult(intent, fontFolderRequestCode) + } catch (e: java.lang.Exception) { + e.printStackTrace() + requireContext().toast(e.localizedMessage ?: "ERROR") } - .request() + } + 2 -> { + PermissionsCompat.Builder(this@FontSelectDialog) + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { + FileChooserDialog.show( + childFragmentManager, + fontFolderRequestCode, + mode = FileChooserDialog.DIRECTORY + ) + } + .request() + } } } - } - }.show() + }.show() + } } @SuppressLint("DefaultLocale") private fun getFontFiles(doc: DocumentFile) { - launch(IO) { + execute { val docItems = DocumentUtils.listFiles(App.INSTANCE, doc.uri) fontCacheFolder.listFiles()?.forEach { fontFile -> var contain = false @@ -177,6 +182,8 @@ class FontSelectDialog : BaseDialogFragment(), } catch (e: Exception) { toast(e.localizedMessage ?: "") } + }.onError { + toast("getFontFiles:${it.localizedMessage}") } } @@ -204,12 +211,9 @@ class FontSelectDialog : BaseDialogFragment(), launch(IO) { file.copyTo(FileUtils.createFileIfNotExist(fontFolder, file.name), true) .absolutePath.let { path -> - val cb = (parentFragment as? CallBack) ?: (activity as? CallBack) - cb?.let { - if (it.curFontPath != path) { - withContext(Main) { - it.selectFile(path) - } + if (curFilePath != path) { + withContext(Main) { + callBack?.selectFile(path) } } } @@ -217,12 +221,6 @@ class FontSelectDialog : BaseDialogFragment(), } } - override fun curFilePath(): String { - return (parentFragment as? CallBack)?.curFontPath - ?: (activity as? CallBack)?.curFontPath - ?: "" - } - override fun onFilePicked(requestCode: Int, currentPath: String) { when (requestCode) { fontFolderRequestCode -> { @@ -255,6 +253,19 @@ class FontSelectDialog : BaseDialogFragment(), } } + private fun onDefaultFontChange() { + if (curFilePath == "") { + postEvent(EventBus.UP_CONFIG, true) + } else { + callBack?.selectFile("") + } + } + + override val curFilePath: String get() = callBack?.curFontPath ?: "" + + private val callBack: CallBack? + get() = (parentFragment as? CallBack) ?: (activity as? CallBack) + interface CallBack { fun selectFile(path: String) val curFontPath: String diff --git a/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt b/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt index 253c1bef3..7f14686e8 100644 --- a/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt @@ -24,7 +24,11 @@ import io.legado.app.utils.sp import kotlin.math.min import kotlin.math.pow -class CircleImageView : AppCompatImageView { +class CircleImageView(context: Context, attrs: AttributeSet) : + AppCompatImageView( + context, + attrs + ) { private val mDrawableRect = RectF() private val mBorderRect = RectF() @@ -59,10 +63,9 @@ class CircleImageView : AppCompatImageView { private var mBorderOverlay: Boolean = false var isDisableCircularTransformation: Boolean = false set(disableCircularTransformation) { - if (isDisableCircularTransformation == disableCircularTransformation) { + if (field == disableCircularTransformation) { return } - field = disableCircularTransformation initializeBitmap() } @@ -85,7 +88,6 @@ class CircleImageView : AppCompatImageView { if (circleBackgroundColor == mCircleBackgroundColor) { return } - mCircleBackgroundColor = circleBackgroundColor mCircleBackgroundPaint.color = circleBackgroundColor invalidate() @@ -117,23 +119,14 @@ class CircleImageView : AppCompatImageView { private var textColor = context.getCompatColor(R.color.tv_text_default) - constructor(context: Context) : super(context) { - init() - } - - @JvmOverloads - constructor(context: Context, attrs: AttributeSet, defStyle: Int = 0) : super( - context, - attrs, - defStyle - ) { - - val a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0) - - mBorderWidth = a.getDimensionPixelSize( - R.styleable.CircleImageView_civ_border_width, - DEFAULT_BORDER_WIDTH - ) + init { + super.setScaleType(SCALE_TYPE) + val a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView) + mBorderWidth = + a.getDimensionPixelSize( + R.styleable.CircleImageView_civ_border_width, + DEFAULT_BORDER_WIDTH + ) mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR) mBorderOverlay = @@ -152,11 +145,6 @@ class CircleImageView : AppCompatImageView { } a.recycle() - init() - } - - private fun init() { - super.setScaleType(SCALE_TYPE) mReady = true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt index ff3fa801c..b295e5ad3 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt @@ -167,8 +167,14 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference dialogIconNames?.let { val resId = context.resources .getIdentifier(it[index].toString(), "mipmap", context.packageName) - val d = context.getCompatDrawable(resId) - icon.setImageDrawable(d) + val d = try { + context.getCompatDrawable(resId) + } catch (e: Exception) { + null + } + d?.let { + icon.setImageDrawable(d) + } } label.isChecked = item.toString() == dialogValue onClick { diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt index b51db7a19..d8b51e90f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt @@ -11,7 +11,7 @@ import io.legado.app.R class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) { init { - widgetLayoutResource = R.layout.item_text + widgetLayoutResource = R.layout.item_fillet_text } override fun onBindViewHolder(holder: PreferenceViewHolder?) { diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt new file mode 100644 index 000000000..5c9e38e4d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt @@ -0,0 +1,169 @@ +package io.legado.app.ui.widget.recycler + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.util.Log +import android.view.View +import android.widget.LinearLayout +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.roundToInt + + +/** + * 不画最后一条分隔线 + */ +class DividerNoLast(context: Context, orientation: Int) : + RecyclerView.ItemDecoration() { + + companion object { + const val HORIZONTAL = LinearLayout.HORIZONTAL + const val VERTICAL = LinearLayout.VERTICAL + } + + private val tag = "DividerItem" + private val attrs = intArrayOf(android.R.attr.listDivider) + + private var mDivider: Drawable? = null + + /** + * Current orientation. Either [.HORIZONTAL] or [.VERTICAL]. + */ + private var mOrientation = 0 + + private val mBounds = Rect() + + init { + val a = context.obtainStyledAttributes(attrs) + mDivider = a.getDrawable(0) + if (mDivider == null) { + Log.w( + tag, "@android:attr/listDivider was not set in the theme used for this " + + "DividerItemDecoration. Please set that attribute all call setDrawable()" + ) + } + a.recycle() + setOrientation(orientation) + } + + /** + * Sets the orientation for this divider. This should be called if + * [RecyclerView.LayoutManager] changes orientation. + * + * @param orientation [.HORIZONTAL] or [.VERTICAL] + */ + fun setOrientation(orientation: Int) { + require(!(orientation != HORIZONTAL && orientation != VERTICAL)) { "Invalid orientation. It should be either HORIZONTAL or VERTICAL" } + mOrientation = orientation + } + + /** + * Sets the [Drawable] for this divider. + * + * @param drawable Drawable that should be used as a divider. + */ + fun setDrawable(drawable: Drawable) { + requireNotNull(drawable) { "Drawable cannot be null." } + mDivider = drawable + } + + /** + * @return the [Drawable] for this divider. + */ + fun getDrawable(): Drawable? { + return mDivider + } + + override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + if (parent.layoutManager == null || mDivider == null) { + return + } + if (mOrientation == VERTICAL) { + drawVertical(c, parent) + } else { + drawHorizontal(c, parent) + } + } + + private fun drawVertical( + canvas: Canvas, + parent: RecyclerView + ) { + canvas.save() + val left: Int + val right: Int + if (parent.clipToPadding) { + left = parent.paddingLeft + right = parent.width - parent.paddingRight + canvas.clipRect( + left, parent.paddingTop, right, + parent.height - parent.paddingBottom + ) + } else { + left = 0 + right = parent.width + } + val childCount = parent.childCount + for (i in 0 until childCount - 1) { + val child = parent.getChildAt(i) + parent.getDecoratedBoundsWithMargins(child, mBounds) + val bottom = mBounds.bottom + child.translationY.roundToInt() + val top = bottom - mDivider!!.intrinsicHeight + mDivider!!.setBounds(left, top, right, bottom) + mDivider!!.draw(canvas) + } + canvas.restore() + } + + private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) { + canvas.save() + val top: Int + val bottom: Int + if (parent.clipToPadding) { + top = parent.paddingTop + bottom = parent.height - parent.paddingBottom + canvas.clipRect( + parent.paddingLeft, top, + parent.width - parent.paddingRight, bottom + ) + } else { + top = 0 + bottom = parent.height + } + val childCount = parent.childCount + for (i in 0 until childCount - 1) { + val child = parent.getChildAt(i) + parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds) + val right = mBounds.right + child.translationX.roundToInt() + val left = right - mDivider!!.intrinsicWidth + mDivider!!.setBounds(left, top, right, bottom) + mDivider!!.draw(canvas) + } + canvas.restore() + } + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, + state: RecyclerView.State + ) { + if (mDivider == null) { + outRect[0, 0, 0] = 0 + return + } + + if (mOrientation == VERTICAL) { + outRect[0, 0, 0] = mDivider!!.intrinsicHeight + } else { + val childAdapterPosition = parent.getChildAdapterPosition(view) + val lastCount = parent.adapter!!.itemCount - 1 + //如果不是最后一条 正常赋值 如果是最后一条 赋值为0 + if (childAdapterPosition != lastCount) { + outRect[0, 0, mDivider!!.intrinsicWidth] = 0 + } else { + outRect[0, 0, 0] = 0 + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/RecyclerViewExtensions.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/VerticalDivider.kt similarity index 52% rename from app/src/main/java/io/legado/app/utils/RecyclerViewExtensions.kt rename to app/src/main/java/io/legado/app/ui/widget/recycler/VerticalDivider.kt index 7dbed87ce..2ea450d4c 100644 --- a/app/src/main/java/io/legado/app/utils/RecyclerViewExtensions.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/VerticalDivider.kt @@ -1,15 +1,16 @@ -package io.legado.app.utils +package io.legado.app.ui.widget.recycler +import android.content.Context import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.RecyclerView import io.legado.app.R +class VerticalDivider(context: Context) : DividerItemDecoration(context, VERTICAL) { -fun RecyclerView.getVerticalDivider(): DividerItemDecoration { - return DividerItemDecoration(context, DividerItemDecoration.VERTICAL).apply { + init { ContextCompat.getDrawable(context, R.drawable.ic_divider)?.let { this.setDrawable(it) } } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt index 00f1d4c58..371be8122 100644 --- a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt +++ b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt @@ -121,7 +121,12 @@ object BitmapUtils { * @throws IOException */ @Throws(IOException::class) - fun decodeBitmap(context: Context, fileNameInAssets: String, width: Int, height: Int): Bitmap? { + fun decodeAssetsBitmap( + context: Context, + fileNameInAssets: String, + width: Int, + height: Int + ): Bitmap? { var inputStream = context.assets.open(fileNameInAssets) val op = BitmapFactory.Options() @@ -147,7 +152,7 @@ object BitmapUtils { //图片不被压缩 fun convertViewToBitmap(view: View, bitmapWidth: Int, bitmapHeight: Int): Bitmap { - val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888) + val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888) view.draw(Canvas(bitmap)) return bitmap } diff --git a/app/src/main/java/io/legado/app/utils/ColorUtils.kt b/app/src/main/java/io/legado/app/utils/ColorUtils.kt index 3ca7ab5bc..9eceb6724 100644 --- a/app/src/main/java/io/legado/app/utils/ColorUtils.kt +++ b/app/src/main/java/io/legado/app/utils/ColorUtils.kt @@ -4,6 +4,9 @@ import android.graphics.Color import androidx.annotation.ColorInt import androidx.annotation.FloatRange +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt @Suppress("unused") object ColorUtils { @@ -53,7 +56,7 @@ object ColorUtils { @ColorInt fun adjustAlpha(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) factor: Float): Int { - val alpha = Math.round(Color.alpha(color) * factor) + val alpha = (Color.alpha(color) * factor).roundToInt() val red = Color.red(color) val green = Color.green(color) val blue = Color.blue(color) @@ -62,7 +65,7 @@ object ColorUtils { @ColorInt fun withAlpha(@ColorInt baseColor: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int { - val a = Math.min(255, Math.max(0, (alpha * 255).toInt())) shl 24 + val a = min(255, max(0, (alpha * 255).toInt())) shl 24 val rgb = 0x00ffffff and baseColor return a + rgb } diff --git a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt index 1ad897272..3436c9d94 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -1,13 +1,13 @@ +@file:Suppress("unused") package io.legado.app.utils import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.content.IntentFilter +import android.content.* import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.net.Uri import android.os.BatteryManager import android.provider.Settings import androidx.annotation.ColorRes @@ -31,7 +31,6 @@ fun Context.getPrefBoolean(key: String, defValue: Boolean = false) = fun Context.putPrefBoolean(key: String, value: Boolean = false) = defaultSharedPreferences.edit { putBoolean(key, value) } - fun Context.getPrefInt(key: String, defValue: Int = 0) = defaultSharedPreferences.getInt(key, defValue) @@ -70,35 +69,27 @@ fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? = /** * 系统息屏时间 */ -fun Context.getScreenOffTime(): Int { - var screenOffTime = 0 - try { - screenOffTime = Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT) - } catch (e: Exception) { - e.printStackTrace() - } - return screenOffTime +val Context.sysScreenOffTime: Int + get() { + var screenOffTime = 0 + try { + screenOffTime = Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT) + } catch (e: Exception) { + e.printStackTrace() + } + return screenOffTime } -fun Context.getStatusBarHeight(): Int { - val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") - return resources.getDimensionPixelSize(resourceId) +val Context.statusBarHeight: Int + get() { + val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") + return resources.getDimensionPixelSize(resourceId) } -fun Context.getNavigationBarHeight(): Int { - val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") - return resources.getDimensionPixelSize(resourceId) -} - -fun Context.shareText(title: String, text: String) { - try { - val textIntent = Intent(Intent.ACTION_SEND) - textIntent.type = "text/plain" - textIntent.putExtra(Intent.EXTRA_TEXT, text) - startActivity(Intent.createChooser(textIntent, title)) - } catch (e: Exception) { - toast(R.string.can_not_share) - } +val Context.navigationBarHeight: Int + get() { + val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") + return resources.getDimensionPixelSize(resourceId) } @SuppressLint("SetWorldReadable") @@ -132,6 +123,19 @@ fun Context.shareWithQr(title: String, text: String) { } } +fun Context.sendToClip(text: String) { + val clipboard = + getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val clipData = ClipData.newPlainText(null, text) + clipboard?.let { + clipboard.setPrimaryClip(clipData) + toast(R.string.copy_complete) + } +} + +/** + * 系统是否暗色主题 + */ fun Context.sysIsDarkMode(): Boolean { val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK return mode == Configuration.UI_MODE_NIGHT_YES @@ -140,8 +144,31 @@ fun Context.sysIsDarkMode(): Boolean { /** * 获取电量 */ -fun Context.getBettery(): Int { - val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) - val batteryStatus = registerReceiver(null, iFilter) - return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 +val Context.sysBattery: Int + get() { + val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val batteryStatus = registerReceiver(null, iFilter) + return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 +} + +fun Context.openUrl(url: String) { + openUrl(Uri.parse(url)) +} + +fun Context.openUrl(uri: Uri) { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = uri + if (intent.resolveActivity(packageManager) != null) { + try { + startActivity(intent) + } catch (e: Exception) { + toast(e.localizedMessage ?: "open url error") + } + } else { + try { + startActivity(Intent.createChooser(intent, "请选择浏览器")) + } catch (e: Exception) { + toast(e.localizedMessage ?: "open url error") + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/FloatExtensions.kt b/app/src/main/java/io/legado/app/utils/FloatExtensions.kt index af9576c00..4e3c7f9ed 100644 --- a/app/src/main/java/io/legado/app/utils/FloatExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/FloatExtensions.kt @@ -2,14 +2,13 @@ package io.legado.app.utils import android.content.res.Resources - -val Float.dp: Float // [xxhdpi](360 -> 1080) +val Float.dp: Float get() = android.util.TypedValue.applyDimension( android.util.TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics ) -val Float.sp: Float // [xxhdpi](360 -> 1080) +val Float.sp: Float get() = android.util.TypedValue.applyDimension( android.util.TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics ) diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index 02faf53c3..3f4f40ee2 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -1,7 +1,5 @@ package io.legado.app.utils -// import org.apache.commons.text.StringEscapeUtils - fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() fun String?.isContentPath(): Boolean = this?.startsWith("content://") == true @@ -50,4 +48,14 @@ fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array = run { this.split(regex, limit).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() } - +fun Char?.isHAN(): Boolean { + this ?: return false + val ub: Character.UnicodeBlock = Character.UnicodeBlock.of(this) ?: return false + return ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS + || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A + || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B + || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C + || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D + || ub === Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS + || ub === Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT +} diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index 5d191609f..b8a457325 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -7,8 +7,10 @@ import android.os.Build import android.view.View import android.view.View.* import android.view.inputmethod.InputMethodManager +import android.widget.RadioGroup import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.get import io.legado.app.App @@ -50,10 +52,10 @@ fun View.visible() { } fun View.visible(visible: Boolean) { - visibility = if (visible) { - VISIBLE - } else { - INVISIBLE + if (visible && visibility != VISIBLE) { + visibility = VISIBLE + } else if (!visible && visibility == VISIBLE) { + visibility = INVISIBLE } } @@ -69,4 +71,26 @@ fun View.screenshot(): Bitmap? { fun SeekBar.progressAdd(int: Int) { progress += int +} + +fun RadioGroup.getIndexById(id: Int): Int { + for (i in 0 until this.childCount) { + if (id == get(i).id) { + return i + } + } + return 0 +} + +fun RadioGroup.getCheckedIndex(): Int { + for (i in 0 until this.childCount) { + if (checkedRadioButtonId == get(i).id) { + return i + } + } + return 0 +} + +fun RadioGroup.checkByIndex(index: Int) { + check(get(index).id) } \ No newline at end of file diff --git a/app/src/main/res/drawable/recyclerview_divider_horizontal.xml b/app/src/main/res/drawable/recyclerview_divider_horizontal.xml new file mode 100644 index 000000000..2d2d95362 --- /dev/null +++ b/app/src/main/res/drawable/recyclerview_divider_horizontal.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/recyclerview_item_divider.xml b/app/src/main/res/drawable/recyclerview_divider_vertical.xml similarity index 100% rename from app/src/main/res/drawable/recyclerview_item_divider.xml rename to app/src/main/res/drawable/recyclerview_divider_vertical.xml diff --git a/app/src/main/res/layout/dialog_bookshelf_config.xml b/app/src/main/res/layout/dialog_bookshelf_config.xml new file mode 100644 index 000000000..944913f4c --- /dev/null +++ b/app/src/main/res/layout/dialog_bookshelf_config.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_number_picker.xml b/app/src/main/res/layout/dialog_number_picker.xml index 5a471be84..f54fd0a00 100644 --- a/app/src/main/res/layout/dialog_number_picker.xml +++ b/app/src/main/res/layout/dialog_number_picker.xml @@ -7,6 +7,7 @@ android:id="@+id/number_picker" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center" /> + android:layout_gravity="center" + android:scrollbars="none" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_read_aloud.xml b/app/src/main/res/layout/dialog_read_aloud.xml index 37c5cc0d6..ea04696d4 100644 --- a/app/src/main/res/layout/dialog_read_aloud.xml +++ b/app/src/main/res/layout/dialog_read_aloud.xml @@ -23,7 +23,7 @@ android:contentDescription="@string/chapter_list" android:src="@drawable/ic_toc" android:tint="@color/tv_text_default" - android:tooltipText="@string/auto_next_page" + android:tooltipText="@string/chapter_list" app:backgroundTint="@color/background_menu" app:elevation="2dp" app:fabSize="mini" @@ -43,7 +43,7 @@ android:contentDescription="@string/to_backstage" android:src="@drawable/ic_visibility_off" android:tint="@color/tv_text_default" - android:tooltipText="@string/replace_rule_title" + android:tooltipText="@string/to_backstage" app:backgroundTint="@color/background_menu" app:elevation="2dp" app:fabSize="mini" @@ -79,9 +79,11 @@ android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:background="?android:attr/selectableItemBackgroundBorderless" + android:tooltipText="@string/menu" android:contentDescription="@string/menu" android:src="@drawable/ic_menu" - android:tint="@color/tv_text_default" /> + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> @@ -160,8 +172,11 @@ + android:tint="@color/tv_text_default" + tools:ignore="UnusedAttribute" /> + + + @@ -77,7 +92,6 @@ android:paddingBottom="3dp" android:gravity="center" android:textSize="14sp" - app:layout_constraintLeft_toRightOf="@+id/tv_padding" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -133,8 +147,8 @@ android:id="@+id/tv_page_anim" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="10dp" - android:paddingEnd="10dp" + android:paddingStart="6dp" + android:paddingEnd="6dp" android:text="@string/page_anim" app:layout_constraintTop_toBottomOf="@+id/vw_bg_fg" /> @@ -142,8 +156,8 @@ android:id="@+id/rg_page_anim" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="16dp" - android:paddingRight="16dp" + android:paddingLeft="10dp" + android:paddingRight="10dp" android:orientation="horizontal" android:gravity="center" app:layout_constraintTop_toBottomOf="@id/tv_page_anim"> @@ -219,17 +233,43 @@ + app:layout_constraintTop_toBottomOf="@+id/vw_bg_fg1" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toLeftOf="@+id/tv_share_layout" /> + + + + @@ -25,6 +28,7 @@ android:id="@+id/dsb_header_padding_bottom" android:layout_width="match_parent" android:layout_height="wrap_content" + android:visibility="gone" app:title="@string/padding_bottom" app:max="100" /> @@ -32,6 +36,7 @@ android:id="@+id/dsb_header_padding_left" android:layout_width="match_parent" android:layout_height="wrap_content" + android:visibility="gone" app:title="@string/padding_left" app:max="100" /> @@ -39,13 +44,14 @@ android:id="@+id/dsb_header_padding_right" android:layout_width="match_parent" android:layout_height="wrap_content" + android:visibility="gone" app:title="@string/padding_right" app:max="100" /> diff --git a/app/src/main/res/layout/dialog_toc_regex.xml b/app/src/main/res/layout/dialog_toc_regex.xml new file mode 100644 index 000000000..43d0c5d3b --- /dev/null +++ b/app/src/main/res/layout/dialog_toc_regex.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_toc_regex_edit.xml b/app/src/main/res/layout/dialog_toc_regex_edit.xml new file mode 100644 index 000000000..07c0816a4 --- /dev/null +++ b/app/src/main/res/layout/dialog_toc_regex_edit.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_book_source.xml b/app/src/main/res/layout/item_book_source.xml index ee1174355..3ae65e3b9 100644 --- a/app/src/main/res/layout/item_book_source.xml +++ b/app/src/main/res/layout/item_book_source.xml @@ -62,6 +62,7 @@ android:id="@+id/iv_explore" android:layout_width="8dp" android:layout_height="8dp" + android:contentDescription="@string/more_menu" android:src="@color/md_green_600" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/item_fillet_text.xml b/app/src/main/res/layout/item_fillet_text.xml new file mode 100644 index 000000000..331bd8a2c --- /dev/null +++ b/app/src/main/res/layout/item_fillet_text.xml @@ -0,0 +1,15 @@ + + diff --git a/app/src/main/res/layout/item_group_manage.xml b/app/src/main/res/layout/item_group_manage.xml index d7d85630a..763a19acd 100644 --- a/app/src/main/res/layout/item_group_manage.xml +++ b/app/src/main/res/layout/item_group_manage.xml @@ -4,6 +4,7 @@ android:layout_height="wrap_content" android:background="@color/background" android:padding="8dp" + android:gravity="center_vertical" android:orientation="horizontal"> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_action_menu.xml b/app/src/main/res/layout/popup_action_menu.xml new file mode 100644 index 000000000..c6b9067d7 --- /dev/null +++ b/app/src/main/res/layout/popup_action_menu.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/view_detail_seek_bar.xml b/app/src/main/res/layout/view_detail_seek_bar.xml index 57261a37a..af87a83bb 100644 --- a/app/src/main/res/layout/view_detail_seek_bar.xml +++ b/app/src/main/res/layout/view_detail_seek_bar.xml @@ -15,9 +15,10 @@ tools:ignore="HardcodedText" /> @@ -31,9 +32,10 @@ android:max="10" /> diff --git a/app/src/main/res/layout/view_select_action_bar.xml b/app/src/main/res/layout/view_select_action_bar.xml index 97d775dc3..e725a57b7 100644 --- a/app/src/main/res/layout/view_select_action_bar.xml +++ b/app/src/main/res/layout/view_select_action_bar.xml @@ -46,10 +46,12 @@ android:layout_width="36dp" android:layout_height="36dp" android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/more_menu" + android:tooltipText="@string/more_menu" android:padding="6dp" android:src="@drawable/ic_more_vert" android:tint="@color/tv_text_default" android:visibility="gone" - tools:ignore="RtlHardcoded" /> + tools:ignore="RtlHardcoded,UnusedAttribute" /> \ No newline at end of file diff --git a/app/src/main/res/menu/content_select_action.xml b/app/src/main/res/menu/content_select_action.xml index d83b82ef3..d11ee6154 100644 --- a/app/src/main/res/menu/content_select_action.xml +++ b/app/src/main/res/menu/content_select_action.xml @@ -1,9 +1,19 @@ - + + android:title="@string/replace" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/read_book.xml b/app/src/main/res/menu/read_book.xml index b27cd41ff..5d776e519 100644 --- a/app/src/main/res/menu/read_book.xml +++ b/app/src/main/res/menu/read_book.xml @@ -43,7 +43,7 @@ diff --git a/app/src/main/res/menu/txt_toc_regex.xml b/app/src/main/res/menu/txt_toc_regex.xml new file mode 100644 index 000000000..38a50e8cc --- /dev/null +++ b/app/src/main/res/menu/txt_toc_regex.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 87fd0d8e0..f46e94565 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -35,14 +35,6 @@ 9 - - @string/layout_list - @string/layout_grid3 - @string/layout_grid4 - @string/layout_grid5 - @string/layout_grid6 - - @string/indent_0 @string/indent_1 @@ -111,18 +103,6 @@ @string/screen_sensor - - @string/bookshelf_px_0 - @string/bookshelf_px_1 - @string/bookshelf_px_2 - - - - 0 - 1 - 2 - - @@ -152,4 +132,9 @@ 简体转繁体 + + 系统默认字体 + 系统衬线字体 + 系统等宽等宽 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6eb36afe6..7e9540a2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,12 +47,13 @@ 书架还空着,先去添加吧! 搜索 下载 - 列表视图 - 网格视图三列 - 网格视图四列 - 网格视图五列 - 网格视图六列 + 列表 + 网格三列 + 网格四列 + 网格五列 + 网格六列 书架布局 + 视图 书城 添加本地 书源 @@ -86,7 +87,7 @@ 下载选择的章节到本地 换源 - \u3000\u3000这是一款开源的阅读软件,你可以fork我们的代码自己编译APK。欢迎提交代码帮助改善应用。\n\u3000\u3000公众号[开源阅读软件]! + \u3000\u3000这是一款使用Kotlin全新开发的开源的阅读软件,欢迎您的加入。关注公众号[开源阅读软件]! 阅读3.0下载地址:\nhttps://play.google.com/store/apps/details?id=io.legado.app @@ -226,11 +227,11 @@ 来源:%s 本地导入 网络导入 - 书架排序 检查更新间隔 - 按阅读时间排序 - 按更新时间排序 - 手动排序 + 按阅读时间 + 按更新时间 + 按书名 + 手动排序 阅读方式 删除所选 是否确认删除? @@ -334,7 +335,7 @@ 显示所有发现 关闭则只显示勾选源的发现 更新目录 - txt目录正则 + Txt目录正则 设置编码 倒序-顺序 排序 @@ -611,10 +612,21 @@ 图标为矢量图标,Android8.0以前不支持 朗读设置 主界面 - 选择文本 + 长按选择文本 页眉 正文 页角 文本选择结束位置 文本选择开始位置 + 共用布局 + 标题居中 + 浏览器 + 导入默认规则 + 名称 + 正则 + 更多菜单 + + + 其它朗读设置 + 系统内置字体样式 diff --git a/build.gradle b/build.gradle index a6c94a0db..5cb5d5179 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.3' classpath 'com.google.gms:google-services:4.3.3' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7002c3786..20b544410 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Aug 21 20:30:49 CST 2019 +#Tue Feb 25 08:10:32 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip