Merge pull request #19 from gedoor/master

up
pull/379/head
口口吕 5 years ago committed by GitHub
commit b6dd225818
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      app/build.gradle
  2. 29
      app/google-services.json
  3. 69
      app/src/debug/google-services.json
  4. 38
      app/src/main/AndroidManifest.xml
  5. 126
      app/src/main/assets/txtTocRule.json
  6. 22
      app/src/main/assets/updateLog.md
  7. 9
      app/src/main/java/io/legado/app/App.kt
  8. 9
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  9. 2
      app/src/main/java/io/legado/app/constant/AppConst.kt
  10. 3
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  11. 1
      app/src/main/java/io/legado/app/data/entities/BaseBook.kt
  12. 2
      app/src/main/java/io/legado/app/data/entities/Book.kt
  13. 12
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  14. 6
      app/src/main/java/io/legado/app/help/AppConfig.kt
  15. 7
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  16. 14
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  17. 16
      app/src/main/java/io/legado/app/help/storage/OldRule.kt
  18. 4
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  19. 77
      app/src/main/java/io/legado/app/model/WebBook.kt
  20. 22
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  21. 45
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  22. 28
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  23. 17
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  24. 8
      app/src/main/java/io/legado/app/ui/about/AboutActivity.kt
  25. 10
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  26. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  27. 32
      app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
  28. 33
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  29. 205
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  30. 39
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  31. 20
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  32. 10
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  33. 15
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  34. 50
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  35. 8
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  36. 83
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt
  37. 6
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt
  38. 19
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  39. 24
      app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt
  40. 8
      app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt
  41. 33
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt
  42. 10
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  43. 2
      app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt
  44. 56
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  45. 46
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  46. 12
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt
  47. 9
      app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt
  48. 17
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  49. 23
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  50. 64
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  51. 12
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt
  52. 19
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  53. 4
      app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt
  54. 111
      app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt
  55. 40
      app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt
  56. 10
      app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt
  57. 7
      app/src/main/java/io/legado/app/utils/BitmapUtils.kt
  58. 20
      app/src/main/java/io/legado/app/utils/ContextExtensions.kt
  59. 12
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  60. 24
      app/src/main/java/io/legado/app/utils/ViewExtensions.kt
  61. 105
      app/src/main/res/layout/dialog_bookshelf_config.xml
  62. 3
      app/src/main/res/layout/dialog_number_picker.xml
  63. 5
      app/src/main/res/layout/dialog_read_book_style.xml
  64. 27
      app/src/main/res/layout/popup_action_menu.xml
  65. 3
      app/src/main/res/menu/content_select_action.xml
  66. 25
      app/src/main/res/values/arrays.xml
  67. 22
      app/src/main/res/values/strings.xml
  68. 6
      app/src/main/res/xml/pref_config_read.xml

@ -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'
}

@ -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"

@ -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"
}

@ -123,9 +123,6 @@
<activity
android:name=".ui.config.ConfigActivity"
android:launchMode="singleTask" />
<activity
android:name=".ui.replacerule.ReplaceRuleActivity"
android:launchMode="singleTask" />
<activity
android:name="io.legado.app.ui.book.search.SearchActivity"
android:launchMode="singleTask" />
@ -135,24 +132,51 @@
<activity
android:name=".ui.about.DonateActivity"
android:launchMode="singleTask" />
<activity android:name=".ui.book.arrange.ArrangeBookActivity" />
<activity android:name=".ui.book.source.debug.BookSourceDebugActivity" />
<!--书源管理-->
<activity android:name=".ui.book.source.manage.BookSourceActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="booksource"
android:scheme="yuedu" />
</intent-filter>
</activity>
<!--订阅源管理-->
<activity android:name=".ui.rss.source.manage.RssSourceActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="rsssource"
android:scheme="yuedu" />
</intent-filter>
</activity>
<!--替换规则界面-->
<activity
android:name=".ui.replacerule.ReplaceRuleActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="replace"
android:scheme="yuedu" />
</intent-filter>
</activity>
<activity android:name=".ui.book.arrange.ArrangeBookActivity" />
<activity android:name=".ui.book.source.debug.BookSourceDebugActivity" />
<activity android:name=".ui.chapterlist.ChapterListActivity" />
<activity android:name=".ui.rss.read.ReadRssActivity" />
<activity android:name=".ui.importbook.ImportBookActivity" />
<activity android:name=".ui.explore.ExploreShowActivity" />
<activity android:name=".ui.rss.source.manage.RssSourceActivity" />
<activity android:name=".ui.rss.source.debug.RssSourceDebugActivity" />
<activity android:name=".ui.rss.article.RssArticlesActivity" />
<activity android:name=".ui.rss.favorites.RssFavoritesActivity" />

@ -1,62 +1,68 @@
[
{
"enable": true,
"name": "数字 分隔符 标题名称",
"rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$",
"serialNumber": 0
},
{
"enable": true,
"name": "目录",
"rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 1
},
{
"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)(?:前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 3
},
{
"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": "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}正文\\s{1,4}.{0,20}$",
"serialNumber": 6
},
{
"enable": true,
"name": "特殊符号 序号 标题",
"rule": "^[ \\t]{0,4}[〈〖〔【][第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节][\\.:: \f\t].{0,30}$",
"serialNumber": 7
},
{
"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
}
{
"enable": true,
"name": "目录",
"rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 0
},
{
"enable": false,
"name": "目录(不匹配行前空白)",
"rule": "^(?<=\\s)(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 1
},
{
"enable": false,
"name": "目录(去简介)",
"rule": "^(?<=\\s)(?:前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 2
},
{
"enable": false,
"name": "目录(古典、轻小说备用)",
"rule": "^[ \\t]{0,4}(?:前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$",
"serialNumber": 3
},
{
"enable": true,
"name": "数字 分隔符 标题名称",
"rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,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": 6
},
{
"enable": true,
"name": "特殊符号 序号 标题",
"rule": "^[ \\t]{0,4}[〈〖〔【][第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节][\\.:: \f\t].{0,30}$",
"serialNumber": 7
},
{
"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
},
{
"enable":false,
"name": "顶格标题",
"rule": "^\\S.{1,20}$",
"serialNumber": 10
}
]

@ -2,6 +2,28 @@
* 旧版数据导入教程:
* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/02/29**
* 添加书源一键导入
* 修复主题模式跟随系统
* 修复书源校验
* 添加书架排序
* 添加点击翻页开关
* 修复共用布局没有记住配置的bug
**2020/02/28**
* 解决阅读界面部分字体超出范围的问题
* 修复背景切换有时空白的bug
* 修复滚动翻页问题
**2020/02/27**
* 修复bug,边距调节,换源等一些bug,记不清了
* 修复默认字体问题
* 改了下包名,好上架应用市场
**2020/02/26**
* 修复仿真翻页
* 功能添加: 选择默认字体时, 可选择字体默认字体(非衬线), 系统衬线字体, 系统等宽字体by hingbong
**2020/02/25**
* 优化文本选择和滚动,感觉很完美了

@ -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() {

@ -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 <T> execute(
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(scope, context) { block() }
}
}

@ -22,7 +22,7 @@ object AppConst {
const val UA_NAME = "User-Agent"
val userAgent: String by lazy {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
}
val SCRIPT_ENGINE: ScriptEngine by lazy {

@ -5,6 +5,7 @@ object PreferKey {
const val themeMode = "themeMode"
const val downloadPath = "downloadPath"
const val hideStatusBar = "hideStatusBar"
const val clickTurnPage = "clickTurnPage"
const val clickAllNext = "clickAllNext"
const val hideNavigationBar = "hideNavigationBar"
const val precisionSearch = "precisionSearch"
@ -16,6 +17,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"
@ -38,4 +40,5 @@ object PreferKey {
const val bodyIndent = "textIndent"
const val shareLayout = "shareLayout"
const val readStyleSelect = "readStyleSelect"
const val systemTypefaces = "system_typefaces"
}

@ -3,6 +3,7 @@ package io.legado.app.data.entities
import io.legado.app.utils.splitNotBlank
interface BaseBook {
var bookUrl: String
var variableMap: HashMap<String, String>?
var kind: String?
var wordCount: String?

@ -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 本地书籍文件名

@ -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<String>? = null
private set
val origins: LinkedHashSet<String> by lazy { linkedSetOf(origin) }
fun addOrigin(origin: String) {
if (origins == null) {
origins = linkedSetOf(this.origin)
}
origins?.add(origin)
origins.add(origin)
}
fun getDisplayLastChapterTitle(): String {

@ -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)
}
}

@ -34,7 +34,7 @@ object ReadBookConfig {
var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
set(value) {
field = value
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout)
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value)
}
var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
set(value) {
@ -43,6 +43,7 @@ object ReadBookConfig {
App.INSTANCE.putPrefInt(PreferKey.pageAnim, value)
}
var isScroll = pageAnim == 3
val clickTurnPage get() = App.INSTANCE.getPrefBoolean(PreferKey.clickTurnPage, true)
var bg: Drawable? = null
init {
@ -215,7 +216,7 @@ object ReadBookConfig {
class Config(
private var bgStr: String = "#EEEEEE",//白天背景
private var bgStrNight: String = "#000000",//夜间背景
private var bgType: Int = 0,//白天背景类型
private var bgType: Int = 0,//白天背景类型 0:颜色, 1:assets图片, 2其它图片
private var bgTypeNight: Int = 0,//夜间背景类型
private var darkStatusIcon: Boolean = true,//白天是否暗色状态栏
private var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏
@ -299,7 +300,7 @@ object ReadBookConfig {
1 -> {
BitmapDrawable(
resources,
BitmapUtils.decodeBitmap(
BitmapUtils.decodeAssetsBitmap(
App.INSTANCE,
"bg" + File.separator + bgStr(),
width,

@ -100,17 +100,9 @@ object Backup {
for (fileName in backupFileNames) {
val file = File(backupPath + File.separator + fileName)
if (file.exists()) {
var doc = treeDoc.findFile(fileName)
if (null != doc && doc.exists()) {
doc.delete()
}
doc = treeDoc.createFile("", fileName)
doc?.let {
DocumentUtils.writeText(
context,
file.readText(),
it.uri
)
treeDoc.findFile(fileName)?.delete()
treeDoc.createFile("", fileName)?.let {
DocumentUtils.writeBytes(context, file.readBytes(), it.uri)
}
}
}

@ -100,15 +100,22 @@ object OldRule {
if (oldRule.isNullOrBlank()) return null
var newRule = oldRule
var reverse = false
var allinone = false
if (oldRule.startsWith("-")) {
reverse = true
newRule = oldRule.substring(1)
}
if (newRule.startsWith("+")) {
allinone = true
newRule = newRule.substring(1)
}
if (!newRule.startsWith("@CSS:", true) &&
!newRule.startsWith("@XPath:", true) &&
!newRule.startsWith("//") &&
!newRule.startsWith("##") &&
!newRule.startsWith(":")
!newRule.startsWith(":") &&
!newRule.contains("@js:",true) &&
!newRule.contains("<js>",true)
) {
if (newRule.contains("#") && !newRule.contains("##")) {
newRule = oldRule.replace("#", "##")
@ -134,8 +141,11 @@ object OldRule {
newRule = newRule.replace("&", "&&")
}
}
if (allinone) {
newRule = "+" + newRule
}
if (reverse) {
newRule += "-"
newRule = "-" + newRule
}
return newRule
}
@ -145,7 +155,7 @@ object OldRule {
if (!oldUrls.contains("\n") && !oldUrls.contains("&&"))
return toNewUrl(oldUrls)
val urls = oldUrls.split("(&&|\n)+".toRegex())
val urls = oldUrls.split("(&&|\r?\n)+".toRegex())
return urls.map {
toNewUrl(it)?.replace("\n\\s*".toRegex(), "")
}.joinToString("\n")

@ -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
@ -113,6 +115,8 @@ object Restore {
hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
}
ChapterProvider.upStyle()
ReadBook.loadContent()
}
LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon))
}

@ -86,17 +86,18 @@ class WebBook(val bookSource: BookSource) {
): Coroutine<Book> {
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<List<BookChapter>> {
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,

@ -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<List<String>> {
val chapterList = arrayListOf<BookChapter>()
val nextUrlList = arrayListOf<String>()
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)

@ -33,10 +33,14 @@ 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)
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]
@ -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<List<String>> {
val nextUrlList = arrayListOf<String>()
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body, baseUrl)
analyzeRule.chapter = chapter
val nextUrlRule = contentRule.nextContentUrl
if (!nextUrlRule.isNullOrEmpty()) {

@ -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<String>()
private val allIds = LinkedHashSet<String>()
private val checkedIds = LinkedHashSet<String>()
override fun onCreate() {
super.onCreate()
@ -46,12 +47,12 @@ class CheckSourceService : BaseService() {
private fun check(ids: List<String>) {
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<CheckSourceService>(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)

@ -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()

@ -6,7 +6,7 @@ import android.view.MenuItem
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.utils.openUrl
import io.legado.app.utils.shareText
import org.jetbrains.anko.share
class AboutActivity : BaseActivity(R.layout.activity_about) {
@ -27,9 +27,9 @@ class AboutActivity : BaseActivity(R.layout.activity_about) {
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_scoring -> openUrl("market://details?id=$packageName")
R.id.menu_share_it -> shareText(
"App Share",
getString(R.string.app_share_description)
R.id.menu_share_it -> share(
getString(R.string.app_share_description),
getString(R.string.app_name)
)
}
return super.onCompatOptionsItemSelected(item)

@ -95,12 +95,14 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(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()
})

@ -33,7 +33,6 @@ 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
@ -656,7 +655,6 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
upSystemUiVisibility()
page_view.upBg()
page_view.upStyle()
ChapterProvider.upStyle()
if (it) {
ReadBook.loadContent()
} else {
@ -693,7 +691,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
upScreenTimeOut()
}
observeEvent<Boolean>(PreferKey.textSelectAble) {
page_view.upSelectAble(it)
page_view.curPage.upSelectAble(it)
}
}

@ -15,6 +15,8 @@ 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
@ -22,9 +24,11 @@ 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")
@ -48,10 +52,27 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
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) {
onInitializeMenu(menu)
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()
}
}
adapter.setItems(menu.visibleItems)
}
inner class Adapter(context: Context) :
@ -81,6 +102,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
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()) {
@ -98,12 +120,6 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
context.toast(e.localizedMessage ?: "ERROR")
}
}
else -> item.intent?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
it.putExtra(Intent.EXTRA_PROCESS_TEXT, callBack.selectedText)
context.startActivity(it)
}
}
}
callBack.onMenuActionFinally()
}

@ -12,7 +12,6 @@ import io.legado.app.R
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.help.BookHelp
import io.legado.app.help.ImageLoader
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.accentColor
@ -20,6 +19,7 @@ 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.getIndexById
import io.legado.app.utils.getPrefString
import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefString
@ -31,6 +31,8 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick
class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
val callBack get() = activity as? ReadBookActivity
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
@ -121,11 +123,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
}
}
tv_padding.onClick {
val activity = activity
dismiss()
if (activity is ReadBookActivity) {
activity.showPaddingConfig()
}
callBack?.showPaddingConfig()
}
dsb_text_size.onChanged = {
ReadBookConfig.textSize = it + 5
@ -144,15 +143,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
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) {
ReadBookConfig.pageAnim = i
val activity = activity
if (activity is ReadBookActivity) {
activity.page_view.upPageAnim()
}
break
}
rg_page_anim.getIndexById(checkedId).let {
ReadBookConfig.pageAnim = it
callBack?.page_view?.upPageAnim()
}
}
cb_share_layout.onCheckedChangeListener = { checkBox, isChecked ->
@ -187,10 +180,7 @@ 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
}
@ -219,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))
}
}

@ -8,10 +8,10 @@ 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.TextChapter
import io.legado.app.ui.book.read.page.entities.TextChar
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
@ -78,7 +78,7 @@ object ChapterProvider {
durY = joinBody(text, durY, textPages, pageLines, pageLengths, stringBuilder)
}
}
textPages.last().height = durY
textPages.last().height = durY + 20.dp
textPages.last().text = stringBuilder.toString()
if (pageLines.size < textPages.size) {
pageLines.add(textPages.last().textLines.size)
@ -145,42 +145,15 @@ object ChapterProvider {
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,
start = paddingLeft + x,
end = paddingLeft + x1
)
textLine.textChars.add(textChar)
x = x1
}
addCharsToLineMiddle(textLine, words, titlePaint, desiredWidth, 0f)
} else {
//最后一行
textLine.text = "$words\n"
stringBuilder.append("\n")
var x = if (ReadBookConfig.titleCenter)
(visibleWidth - layout.getLineMax(lineIndex)) / 2
val x = if (ReadBookConfig.titleCenter)
(visibleWidth - layout.getLineWidth(lineIndex)) / 2
else 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,
start = paddingLeft + x,
end = paddingLeft + x1
)
textLine.textChars.add(textChar)
x = x1
}
addCharsToLineLast(textLine, words, stringBuilder, titlePaint, x)
}
}
durY += paragraphSpacing
@ -224,80 +197,120 @@ object ChapterProvider {
textLine.lineBase = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent
var words =
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) / bodyIndent.length
for (i in 0..bodyIndent.lastIndex) {
val x1 = x + icw
val textChar = TextChar(
charData = bodyIndent[i].toString(),
start = paddingLeft + x,
end = paddingLeft + x1
)
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)
val x1 = if (i != words.lastIndex) x + cw + d else x + cw
val textChar1 = TextChar(
charData = char,
start = paddingLeft + x,
end = paddingLeft + x1
)
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,
start = paddingLeft + x,
end = paddingLeft + x1
)
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,
start = paddingLeft + x,
end = paddingLeft + x1
)
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
}
}
}
}
/**
* 更新样式
@ -308,7 +321,11 @@ object ChapterProvider {
if (!TextUtils.isEmpty(fontPath)) {
Typeface.createFromFile(fontPath)
} else {
Typeface.SANS_SERIF
when (AppConfig.systemTypefaces) {
1 -> Typeface.SERIF
2 -> Typeface.MONOSPACE
else -> Typeface.SANS_SERIF
}
}
} catch (e: Exception) {
App.INSTANCE.removePref(PreferKey.readBookFont)

@ -47,17 +47,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
ChapterProvider.viewWidth = w
ChapterProvider.viewHeight = h
ChapterProvider.upSize()
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)
ChapterProvider.viewWidth = w
ChapterProvider.viewHeight = h
ChapterProvider.upSize()
upVisibleRect()
textPage.format()
}
@ -160,15 +164,23 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
pageOffset += offset
if (pageOffset > 0) {
pageFactory.moveToPrev()
textPage = pageFactory.currentPage
pageOffset -= textPage.height
upView?.invoke(textPage)
if (!pageFactory.hasPrev()) {
pageOffset = 0f
} else {
pageFactory.moveToPrev()
textPage = pageFactory.currentPage
pageOffset -= textPage.height
upView?.invoke(textPage)
}
} else if (pageOffset < -textPage.height) {
pageOffset += textPage.height
pageFactory.moveToNext()
textPage = pageFactory.currentPage
upView?.invoke(textPage)
if (!pageFactory.hasNext()) {
pageOffset = -textPage.height.toFloat()
} else {
pageOffset += textPage.height
pageFactory.moveToNext()
textPage = pageFactory.currentPage
upView?.invoke(textPage)
}
}
invalidate()
}
@ -185,6 +197,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
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()) {

@ -5,8 +5,10 @@ import android.content.Context
import android.graphics.drawable.Drawable
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.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.*
@ -37,8 +39,9 @@ class ContentView(context: Context) : FrameLayout(context) {
tv_bottom_right.typeface = ChapterProvider.typeface
//显示状态栏时隐藏header
if (hideStatusBar) {
ll_header.layoutParams =
ll_header.layoutParams.apply { height = context.statusBarHeight }
ll_header.layoutParams = ll_header.layoutParams.apply {
height = context.statusBarHeight + headerPaddingTop.dp + headerPaddingBottom.dp
}
ll_header.setPadding(
headerPaddingLeft.dp,
headerPaddingTop.dp,
@ -51,18 +54,13 @@ class ContentView(context: Context) : FrameLayout(context) {
ll_header.gone()
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
)
content_text_view.upVisibleRect()
durConfig.textColor().let {
tv_top_left.setTextColor(it)
tv_top_right.setTextColor(it)
@ -94,7 +92,11 @@ class ContentView(context: Context) : FrameLayout(context) {
}
fun setContent(textPage: TextPage) {
tv_bottom_left.text = textPage.title
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)

@ -120,7 +120,7 @@ class PageView(context: Context, attrs: AttributeSet) :
if (noAnim) {
fillPage(PageDelegate.Direction.PREV)
} else {
pageDelegate?.start(PageDelegate.Direction.PREV)
pageDelegate?.prevPageByAnim()
}
}
@ -128,16 +128,12 @@ class PageView(context: Context, attrs: AttributeSet) :
if (noAnim) {
fillPage(PageDelegate.Direction.NEXT)
} else {
pageDelegate?.start(PageDelegate.Direction.NEXT)
pageDelegate?.nextPageByAnim()
}
}
fun upSelectAble(selectAble: Boolean) {
pageDelegate?.upSelectAble(selectAble)
curPage.upSelectAble(selectAble)
}
fun upStyle() {
ChapterProvider.upStyle()
curPage.upStyle()
prevPage.upStyle()
nextPage.upStyle()

@ -52,4 +52,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
}
}
override fun nextPageByAnim() {
if (!hasNext()) return
setDirection(Direction.NEXT)
setBitmap()
setTouchPoint(viewWidth.toFloat(), 0f)
onAnimStart()
}
override fun prevPageByAnim() {
if (!hasPrev()) return
setDirection(Direction.PREV)
setBitmap()
setTouchPoint(0f, 0f)
onAnimStart()
}
}

@ -12,6 +12,7 @@ import android.widget.Scroller
import androidx.annotation.CallSuper
import com.google.android.material.snackbar.Snackbar
import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot
@ -101,10 +102,6 @@ abstract class PageDelegate(protected val pageView: PageView) :
onScroll()
}
fun upSelectAble(selectAble: Boolean) {
detector.setIsLongpressEnabled(selectAble)
}
open fun fling(
startX: Int, startY: Int, velocityX: Int, velocityY: Int,
minX: Int, maxX: Int, minY: Int, maxY: Int
@ -161,34 +158,6 @@ abstract class PageDelegate(protected val pageView: PageView) :
}
}
fun start(direction: Direction) {
if (isStarted) return
if (direction === Direction.NEXT) {
val x = viewWidth.toFloat()
val y = viewHeight.toFloat()
//初始化动画
setStartPoint(x, y, false)
//设置点击点
setTouchPoint(x, y, false)
//设置方向
if (!hasNext()) {
return
}
} else {
val x = 0.toFloat()
val y = viewHeight.toFloat()
//初始化动画
setStartPoint(x, y, false)
//设置点击点
setTouchPoint(x, y, false)
//设置方向方向
if (!hasPrev()) {
return
}
}
onAnimStart()
}
open fun onAnimStart() {}//scroller start
open fun onDraw(canvas: Canvas) {}//绘制
@ -197,6 +166,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
open fun onScroll() {}//移动contentView, slidePage
abstract fun nextPageByAnim()
abstract fun prevPageByAnim()
@CallSuper
open fun setDirection(direction: Direction) {
mDirection = direction
@ -264,21 +237,14 @@ abstract class PageDelegate(protected val pageView: PageView) :
if (centerRectF.contains(x, y)) {
pageView.callBack.clickCenter()
setTouchPoint(x, y)
} else {
} else if (ReadBookConfig.clickTurnPage) {
if (x > viewWidth / 2 ||
AppConfig.clickAllNext
) {
//设置动画方向
if (!hasNext()) return true
setDirection(Direction.NEXT)
setBitmap()
nextPageByAnim()
} else {
if (!hasPrev()) return true
setDirection(Direction.PREV)
setBitmap()
prevPageByAnim()
}
setTouchPoint(x, y)
onAnimStart()
}
return true
}

@ -58,4 +58,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
super.onDestroy()
mVelocity.recycle()
}
override fun nextPageByAnim() {
}
override fun prevPageByAnim() {
}
}

@ -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)
@ -117,7 +115,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
override fun setViewSize(width: Int, height: Int) {
super.setViewSize(width, height)
mMaxLength = hypot(viewWidth.toDouble(), viewWidth.toDouble()).toFloat()
mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat()
}
override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) {
@ -126,7 +124,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
}
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
@ -139,6 +137,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
) {
touchY = 1f
}
pageView.invalidate()
}
override fun setDirection(direction: Direction) {
@ -219,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
}
}
@ -258,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 {
@ -337,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(),
@ -372,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(
@ -400,6 +403,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
canvas.restore()
}
//
private fun drawNextPageAreaAndShadow(
canvas: Canvas,
bitmap: Bitmap?
@ -447,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()
@ -461,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()

@ -2,7 +2,7 @@ package io.legado.app.ui.book.read.page.entities
data class TextChar(
val charData: String,
var selected: Boolean = false,
val start: Float,
val end: Float
var start: Float,
var end: Float,
var selected: Boolean = false
)

@ -8,4 +8,21 @@ data class TextLine(
var lineBottom: Float = 0f,
val isTitle: Boolean = false,
var isReadAloud: Boolean = false
)
) {
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
}
}

@ -16,10 +16,6 @@ class DiffCallBack(private val oldItems: List<SearchBook>, 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<SearchBook>, 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<SearchBook>, 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
}

@ -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(

@ -85,8 +85,8 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
private fun precisionSearch(searchBooks: List<SearchBook>) {
val books = arrayListOf<SearchBook>()
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)
}

@ -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)
}

@ -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()

@ -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)

@ -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<BookshelfViewModel>(R.layout.fragment_b
super.onCompatOptionsItemSelected(item)
when (item.itemId) {
R.id.menu_search -> startActivity<SearchActivity>()
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<ImportBookActivity>()
@ -145,14 +150,35 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(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")

@ -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)
})
}

@ -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)
}
}
}
}

@ -55,6 +55,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
private var dataInit = false
override fun onActivityCreated(savedInstanceState: Bundle?) {
initUriScheme()
initRecyclerView()
initSearchView()
initSelectActionView()
@ -73,6 +74,22 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(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)

@ -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<ReadRssViewModel>(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<ReadRssViewModel>(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
}
}

@ -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<RssSourceEditViewModel>(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<EditEntity> = ArrayList()
@ -99,9 +103,10 @@ class RssSourceEditActivity :
clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr))
}
}
R.id.menu_qr_code_camera -> startActivityForResult<QrCodeActivity>(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
}
@ -204,23 +209,34 @@ class RssSourceEditActivity :
mSoftKeyboardTool?.dismiss()
}
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 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)
}
}
}
}

@ -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<RssSource>(text1)?.let {
finally.invoke(it)
}
}.onError {
toast(it.localizedMessage ?: "Error")
}
}
}

@ -50,13 +50,14 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(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<List<RssSource>>? = null
private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
initUriScheme()
initRecyclerView()
initSearchView()
initLiveDataGroup()
@ -102,6 +103,22 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(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)

@ -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
}
}

@ -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

@ -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) {

@ -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 {

@ -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()

@ -92,17 +92,6 @@ val Context.navigationBarHeight: Int
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)
}
}
@SuppressLint("SetWorldReadable")
fun Context.shareWithQr(title: String, text: String) {
QRCodeEncoder.HINTS[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L
@ -144,6 +133,9 @@ fun Context.sendToClip(text: String) {
}
}
/**
* 系统是否暗色主题
*/
fun Context.sysIsDarkMode(): Boolean {
val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return mode == Configuration.UI_MODE_NIGHT_YES
@ -160,8 +152,12 @@ val Context.sysBattery: Int
}
fun Context.openUrl(url: String) {
openUrl(Uri.parse(url))
}
fun Context.openUrl(uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
intent.data = uri
if (intent.resolveActivity(packageManager) != null) {
try {
startActivity(intent)

@ -48,4 +48,14 @@ fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array<String> = 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
}

@ -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
@ -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)
}

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/ll_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintRight_toLeftOf="@+id/ll_sort"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:text="@string/view"
android:textSize="16sp" />
<RadioGroup
android:id="@+id/rg_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_list" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid3" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid4" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid5" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid6" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_sort"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toRightOf="@+id/ll_layout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent">
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:text="@string/sort"
android:textSize="16sp" />
<RadioGroup
android:id="@+id/rg_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_0" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_1" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_2" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_3" />
</RadioGroup>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -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" />
</FrameLayout>

@ -269,6 +269,7 @@
android:id="@+id/bg0"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/image_cover_default"
app:civ_border_color="@color/tv_text_default"
app:civ_border_width="1dp"
app:text="@string/text"
@ -281,6 +282,7 @@
android:id="@+id/bg1"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/image_cover_default"
app:civ_border_color="@color/tv_text_default"
app:civ_border_width="1dp"
app:text="@string/text"
@ -293,6 +295,7 @@
android:id="@+id/bg2"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/image_cover_default"
app:civ_border_color="@color/tv_text_default"
app:civ_border_width="1dp"
app:text="@string/text"
@ -305,6 +308,7 @@
android:id="@+id/bg3"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/image_cover_default"
app:civ_border_color="@color/tv_text_default"
app:civ_border_width="1dp"
app:text="@string/text"
@ -317,6 +321,7 @@
android:id="@+id/bg4"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/image_cover_default"
app:civ_border_color="@color/tv_text_default"
app:civ_border_width="1dp"
app:text="@string/text"

@ -1,8 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/shape_card_view"
android:padding="5dp" />
android:padding="5dp"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_menu_more"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_more_vert"
android:tint="@color/tv_text_default"
android:visibility="gone"
android:contentDescription="@string/more_menu"
android:layout_gravity="center_vertical" />
</androidx.appcompat.widget.LinearLayoutCompat>

@ -13,4 +13,7 @@
android:id="@+id/menu_browser"
android:title="@string/browser" />
<item
android:id="@+id/menu_share_str"
android:title="@string/share" />
</menu>

@ -35,14 +35,6 @@
<item>9</item>
</string-array>
<string-array name="bookshelf_layout">
<item>@string/layout_list</item>
<item>@string/layout_grid3</item>
<item>@string/layout_grid4</item>
<item>@string/layout_grid5</item>
<item>@string/layout_grid6</item>
</string-array>
<string-array name="indent">
<item>@string/indent_0</item>
<item>@string/indent_1</item>
@ -111,18 +103,6 @@
<item>@string/screen_sensor</item>
</string-array>
<string-array name="bookshelf_px_title">
<item>@string/bookshelf_px_0</item>
<item>@string/bookshelf_px_1</item>
<item>@string/bookshelf_px_2</item>
</string-array>
<string-array name="bookshelf_px_value">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<declare-styleable name="Battery">
<attr name="batteryOrientation">
<enum name="horizontal" value="0" />
@ -152,4 +132,9 @@
<item>简体转繁体</item>
</string-array>
<string-array name="system_typefaces">
<item>系统默认字体</item>
<item>系统衬线字体</item>
<item>系统等宽字体</item>
</string-array>
</resources>

@ -47,12 +47,13 @@
<string name="bookshelf_empty">书架还空着,先去添加吧!</string>
<string name="action_search">搜索</string>
<string name="action_download">下载</string>
<string name="layout_list">列表视图</string>
<string name="layout_grid3">网格视图三列</string>
<string name="layout_grid4">网格视图四列</string>
<string name="layout_grid5">网格视图五列</string>
<string name="layout_grid6">网格视图六列</string>
<string name="layout_list">列表</string>
<string name="layout_grid3">网格三列</string>
<string name="layout_grid4">网格四列</string>
<string name="layout_grid5">网格五列</string>
<string name="layout_grid6">网格六列</string>
<string name="bookshelf_layout">书架布局</string>
<string name="view">视图</string>
<string name="book_library">书城</string>
<string name="book_local">添加本地</string>
<string name="book_source">书源</string>
@ -192,7 +193,7 @@
<string name="img_cover">封面</string>
<string name="book"></string>
<string name="volume_key_page">音量键翻页</string>
<string name="click_open_page">点击翻页</string>
<string name="click_turn_page">点击翻页</string>
<string name="click_all_next_page">点击总是翻下一页</string>
<string name="page_anim">翻页动画</string>
<string name="keep_light">屏幕超时</string>
@ -226,11 +227,11 @@
<string name="origin_show">来源:%s</string>
<string name="import_replace_rule">本地导入</string>
<string name="import_replace_rule_on_line">网络导入</string>
<string name="bookshelf_px">书架排序</string>
<string name="check_update_interval">检查更新间隔</string>
<string name="bookshelf_px_0">按阅读时间排序</string>
<string name="bookshelf_px_1">按更新时间排序</string>
<string name="bookshelf_px_2">手动排序</string>
<string name="bookshelf_px_0">按阅读时间</string>
<string name="bookshelf_px_1">按更新时间</string>
<string name="bookshelf_px_2">按书名</string>
<string name="bookshelf_px_3">手动排序</string>
<string name="read_type">阅读方式</string>
<string name="del_select">删除所选</string>
<string name="del_msg">是否确认删除?</string>
@ -627,4 +628,5 @@
<string name="reduce"></string>
<string name="plus"></string>
<string name="other_aloud_setting">其它朗读设置</string>
<string name="system_typeface">系统内置字体样式</string>
</resources>

@ -28,6 +28,12 @@
android:key="volumeKeyPage"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="true"
android:title="@string/click_turn_page"
android:key="clickTurnPage"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="false"
android:title="@string/click_all_next_page"

Loading…
Cancel
Save