mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
Merge branch 'master' of https://github.com/Xie-Jason/legado
This commit is contained in:
1692
app/schemas/io.legado.app.data.AppDatabase/53.json
Normal file
1692
app/schemas/io.legado.app.data.AppDatabase/53.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,11 +11,11 @@
|
||||
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
|
||||
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
|
||||
|
||||
**2022/09/03**
|
||||
**2022/09/07**
|
||||
|
||||
* 更新cronet: 105.0.5195.79
|
||||
* 更新cronet: 105.0.5195.77
|
||||
* 更新web端书源编辑 by jgckM
|
||||
* 修复仿真翻页点击时翻页动画异常,化仿真翻页点击翻页效果 by 821938089
|
||||
|
||||
**2022/08/31**
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ object PreferKey {
|
||||
const val prevKeys = "prevKeyCodes"
|
||||
const val nextKeys = "nextKeyCodes"
|
||||
const val showDiscovery = "showDiscovery"
|
||||
const val enableReview = "enableReview"
|
||||
const val showRss = "showRss"
|
||||
const val bookshelfLayout = "bookshelfLayout"
|
||||
const val bookshelfSort = "bookshelfSort"
|
||||
|
||||
@@ -20,7 +20,7 @@ val appDb by lazy {
|
||||
}
|
||||
|
||||
@Database(
|
||||
version = 52,
|
||||
version = 53,
|
||||
exportSchema = true,
|
||||
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
|
||||
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
|
||||
@@ -36,7 +36,8 @@ val appDb by lazy {
|
||||
AutoMigration(from = 48, to = 49),
|
||||
AutoMigration(from = 49, to = 50),
|
||||
AutoMigration(from = 50, to = 51),
|
||||
AutoMigration(from = 51, to = 52)
|
||||
AutoMigration(from = 51, to = 52),
|
||||
AutoMigration(from = 52, to = 53)
|
||||
]
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
@@ -50,6 +50,9 @@ interface BookSourceDao {
|
||||
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc")
|
||||
fun flowExplore(): Flow<List<BookSource>>
|
||||
|
||||
// @Query("select * from book_sources where enabledReview = 1 order by customOrder asc")
|
||||
// fun flowReview(): Flow<List<BookSource>>
|
||||
|
||||
@Query("select * from book_sources where loginUrl is not null and loginUrl != ''")
|
||||
fun flowLogin(): Flow<List<BookSource>>
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.legado.app.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class BookChapterReview(
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var bookId: Long = 0,
|
||||
var chapterId: Long = 0,
|
||||
var summaryUrl: String = "",
|
||||
): Parcelable {
|
||||
|
||||
}
|
||||
@@ -37,7 +37,9 @@ data class BookSource(
|
||||
// 是否启用
|
||||
var enabled: Boolean = true,
|
||||
// 启用发现
|
||||
var enabledExplore: Boolean = true,
|
||||
var enabledExplore: Boolean = false,
|
||||
// 启用段评
|
||||
var enabledReview: Boolean? = false,
|
||||
// 启用okhttp CookieJAr 自动保存每次请求的cookie
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
override var enabledCookieJar: Boolean? = false,
|
||||
@@ -74,7 +76,9 @@ data class BookSource(
|
||||
// 目录页规则
|
||||
var ruleToc: TocRule? = null,
|
||||
// 正文页规则
|
||||
var ruleContent: ContentRule? = null
|
||||
var ruleContent: ContentRule? = null,
|
||||
// 段评规则
|
||||
var ruleReview: ReviewRule? = null
|
||||
) : Parcelable, BaseSource {
|
||||
|
||||
override fun getTag(): String {
|
||||
@@ -170,6 +174,13 @@ data class BookSource(
|
||||
return rule
|
||||
}
|
||||
|
||||
fun getReviewRule(): ReviewRule {
|
||||
ruleReview?.let { return it }
|
||||
val rule = ReviewRule()
|
||||
ruleReview = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
fun getDisPlayNameGroup(): String {
|
||||
return if (bookSourceGroup.isNullOrBlank()) {
|
||||
bookSourceName
|
||||
@@ -303,5 +314,13 @@ data class BookSource(
|
||||
fun stringToContentRule(json: String?) =
|
||||
GSON.fromJsonObject<ContentRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun stringToReviewRule(json: String?) =
|
||||
GSON.fromJsonObject<ReviewRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun reviewRuleToString(reviewRule: ReviewRule?): String =
|
||||
GSON.toJson(reviewRule)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.legado.app.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class ReviewRule(
|
||||
var reviewUrl: String? = null, // 段评URL
|
||||
var avatarRule: String? = null, // 段评发布者头像
|
||||
var contentRule: String? = null, // 段评内容
|
||||
var postTimeRule: String? = null, // 段评发布时间
|
||||
var reviewQuoteUrl: String? = null, // 获取段评回复URL
|
||||
|
||||
// 这些功能将在以上功能完成以后实现
|
||||
var voteUpUrl: String? = null, // 点赞URL
|
||||
var voteDownUrl: String? = null, // 点踩URL
|
||||
var postReviewUrl: String? = null, // 发送回复URL
|
||||
var postQuoteUrl: String? = null, // 发送回复段评URL
|
||||
var deleteUrl: String? = null, // 删除段评URL
|
||||
): Parcelable
|
||||
@@ -27,6 +27,7 @@ import splitties.init.appCtx
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.Charset
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -125,7 +126,7 @@ interface JsExtensions {
|
||||
html = html,
|
||||
javaScript = js,
|
||||
headerMap = getSource()?.getHeaderMap(true),
|
||||
tag = getSource()?.getKey()
|
||||
tag = getSource()?.getKey()
|
||||
).getStrResponse().body
|
||||
}
|
||||
}
|
||||
@@ -143,15 +144,18 @@ interface JsExtensions {
|
||||
* 使用内置浏览器打开链接,并等待网页结果
|
||||
*/
|
||||
fun startBrowserAwait(url: String, title: String): StrResponse {
|
||||
return StrResponse(url, SourceVerificationHelp.getVerificationResult(getSource(), url, title, true))
|
||||
return StrResponse(
|
||||
url,
|
||||
SourceVerificationHelp.getVerificationResult(getSource(), url, title, true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开图片验证码对话框,等待返回验证结果
|
||||
*/
|
||||
fun getVerificationCode(imageUrl: String): String {
|
||||
return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false)
|
||||
}
|
||||
fun getVerificationCode(imageUrl: String): String {
|
||||
return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 可从网络,本地文件(阅读私有缓存目录和书籍保存位置支持相对路径)导入JavaScript脚本
|
||||
@@ -203,6 +207,29 @@ interface JsExtensions {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param url 下载地址:可带参数type,文件后缀,不带默认zip
|
||||
* @return 下载的文件相对路径
|
||||
*/
|
||||
fun downloadFile(url: String): String {
|
||||
val analyzeUrl = AnalyzeUrl(url, source = getSource())
|
||||
val type = analyzeUrl.type ?: "zip"
|
||||
val path = FileUtils.getPath(
|
||||
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
|
||||
"${MD5Utils.md5Encode16(url)}.${type}"
|
||||
)
|
||||
FileUtils.delete(path)
|
||||
analyzeUrl.getInputStream().use { iStream ->
|
||||
val file = FileUtils.createFileIfNotExist(path)
|
||||
FileOutputStream(file).use { oStream ->
|
||||
iStream.copyTo(oStream)
|
||||
}
|
||||
}
|
||||
return path.substring(FileUtils.getCachePath().length)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 实现16进制字符串转文件
|
||||
* @param content 需要转成文件的16进制字符串
|
||||
@@ -211,18 +238,18 @@ interface JsExtensions {
|
||||
*/
|
||||
fun downloadFile(content: String, url: String): String {
|
||||
val type = AnalyzeUrl(url, source = getSource()).type ?: return ""
|
||||
val zipPath = FileUtils.getPath(
|
||||
val path = FileUtils.getPath(
|
||||
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
|
||||
"${MD5Utils.md5Encode16(url)}.${type}"
|
||||
)
|
||||
FileUtils.delete(zipPath)
|
||||
val zipFile = FileUtils.createFileIfNotExist(zipPath)
|
||||
FileUtils.delete(path)
|
||||
val file = FileUtils.createFileIfNotExist(path)
|
||||
StringUtils.hexStringToByte(content).let {
|
||||
if (it.isNotEmpty()) {
|
||||
zipFile.writeBytes(it)
|
||||
file.writeBytes(it)
|
||||
}
|
||||
}
|
||||
return zipPath.substring(FileUtils.getCachePath().length)
|
||||
return path.substring(FileUtils.getCachePath().length)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,13 +460,15 @@ interface JsExtensions {
|
||||
}
|
||||
|
||||
/**
|
||||
* js实现文件夹内所有文件读取
|
||||
* js实现文件夹内所有文本文件读取
|
||||
* @param path 文件夹相对路径
|
||||
* @return 所有文件字符串换行连接
|
||||
*/
|
||||
fun getTxtInFolder(unzipPath: String): String {
|
||||
if (unzipPath.isEmpty()) return ""
|
||||
val unzipFolder = getFile(unzipPath)
|
||||
fun getTxtInFolder(path: String): String {
|
||||
if (path.isEmpty()) return ""
|
||||
val folder = getFile(path)
|
||||
val contents = StringBuilder()
|
||||
unzipFolder.listFiles().let {
|
||||
folder.listFiles().let {
|
||||
if (it != null) {
|
||||
for (f in it) {
|
||||
val charsetName = EncodingDetect.getEncode(f)
|
||||
@@ -449,7 +478,7 @@ interface JsExtensions {
|
||||
contents.deleteCharAt(contents.length - 1)
|
||||
}
|
||||
}
|
||||
FileUtils.delete(unzipFolder.absolutePath)
|
||||
FileUtils.delete(folder.absolutePath)
|
||||
return contents.toString()
|
||||
}
|
||||
|
||||
@@ -833,7 +862,7 @@ interface JsExtensions {
|
||||
}
|
||||
|
||||
fun desBase64DecodeToString(
|
||||
data: String, key: String, transformation: String, iv: String
|
||||
data: String, key: String, transformation: String, iv: String
|
||||
): String? {
|
||||
return EncoderUtils.decryptBase64DES(
|
||||
data.encodeToByteArray(),
|
||||
@@ -1028,7 +1057,10 @@ interface JsExtensions {
|
||||
algorithm: String,
|
||||
key: String
|
||||
): String {
|
||||
return Base64.encodeToString(HMac(algorithm, key.toByteArray()).digest(data), Base64.NO_WRAP)
|
||||
return Base64.encodeToString(
|
||||
HMac(algorithm, key.toByteArray()).digest(data),
|
||||
Base64.NO_WRAP
|
||||
)
|
||||
}
|
||||
|
||||
fun md5Encode(str: String): String {
|
||||
|
||||
@@ -151,6 +151,7 @@ object SourceAnalyzer {
|
||||
source.enabled = sourceAny.enabled
|
||||
source.enabledExplore = sourceAny.enabledExplore
|
||||
source.enabledCookieJar = sourceAny.enabledCookieJar
|
||||
source.enabledReview = sourceAny.enabledReview
|
||||
source.concurrentRate = sourceAny.concurrentRate
|
||||
source.header = sourceAny.header
|
||||
source.loginUrl = when (sourceAny.loginUrl) {
|
||||
@@ -206,6 +207,13 @@ object SourceAnalyzer {
|
||||
GSON.fromJsonObject<ContentRule>(GSON.toJson(sourceAny.ruleContent))
|
||||
.getOrNull()
|
||||
}
|
||||
source.ruleReview = if (sourceAny.ruleReview is String) {
|
||||
GSON.fromJsonObject<ReviewRule>(sourceAny.ruleReview.toString())
|
||||
.getOrNull()
|
||||
} else {
|
||||
GSON.fromJsonObject<ReviewRule>(GSON.toJson(sourceAny.ruleReview))
|
||||
.getOrNull()
|
||||
}
|
||||
}
|
||||
source
|
||||
}
|
||||
@@ -221,6 +229,7 @@ object SourceAnalyzer {
|
||||
var customOrder: Int = 0, // 手动排序编号
|
||||
var enabled: Boolean = true, // 是否启用
|
||||
var enabledExplore: Boolean = true, // 启用发现
|
||||
var enabledReview: Boolean = false, // 启用段评
|
||||
var enabledCookieJar: Boolean = false, // 启用CookieJar
|
||||
var concurrentRate: String? = null, // 并发率
|
||||
var header: String? = null, // 请求头
|
||||
@@ -238,7 +247,8 @@ object SourceAnalyzer {
|
||||
var ruleSearch: Any? = null, // 搜索规则
|
||||
var ruleBookInfo: Any? = null, // 书籍信息页规则
|
||||
var ruleToc: Any? = null, // 目录页规则
|
||||
var ruleContent: Any? = null // 正文页规则
|
||||
var ruleContent: Any? = null, // 正文页规则
|
||||
var ruleReview: Any? = null // 段评规则
|
||||
)
|
||||
|
||||
// default规则适配
|
||||
|
||||
@@ -154,6 +154,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
val autoRefreshBook: Boolean
|
||||
get() = appCtx.getPrefBoolean(PreferKey.autoRefresh)
|
||||
|
||||
var enableReview: Boolean
|
||||
get() = appCtx.getPrefBoolean(PreferKey.enableReview, false)
|
||||
set(value) {
|
||||
appCtx.putPrefBoolean(PreferKey.enableReview, value)
|
||||
}
|
||||
|
||||
var threadCount: Int
|
||||
get() = appCtx.getPrefInt(PreferKey.threadCount, 16)
|
||||
set(value) {
|
||||
|
||||
@@ -449,7 +449,7 @@ object ReadBookConfig {
|
||||
var letterSpacing: Float = 0.1f,//字间距
|
||||
var lineSpacingExtra: Int = 12,//行间距
|
||||
var paragraphSpacing: Int = 2,//段距
|
||||
var titleMode: Int = 0,//标题居中
|
||||
var titleMode: Int = 0,//标题位置 0:居左 1:居中 2:隐藏
|
||||
var titleSize: Int = 0,
|
||||
var titleTopSpacing: Int = 0,
|
||||
var titleBottomSpacing: Int = 0,
|
||||
|
||||
@@ -300,6 +300,13 @@ object WebBook {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取段评
|
||||
*/
|
||||
fun getReview() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* 精准搜索
|
||||
*/
|
||||
|
||||
@@ -256,6 +256,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
else -> when (item.itemId) {
|
||||
R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()
|
||||
R.id.menu_re_segment -> item.isChecked = book.getReSegment()
|
||||
R.id.menu_enable_review -> item.isChecked = AppConfig.enableReview
|
||||
R.id.menu_reverse_content -> item.isVisible = onLine
|
||||
}
|
||||
}
|
||||
@@ -356,6 +357,11 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
menu?.findItem(R.id.menu_re_segment)?.isChecked = it.getReSegment()
|
||||
ReadBook.loadContent(false)
|
||||
}
|
||||
R.id.menu_enable_review -> {
|
||||
AppConfig.enableReview = !AppConfig.enableReview
|
||||
menu?.findItem(R.id.menu_enable_review)?.isChecked = AppConfig.enableReview
|
||||
ReadBook.loadContent(false)
|
||||
}
|
||||
R.id.menu_page_anim -> showPageAnimConfig {
|
||||
binding.readView.upPageAnim()
|
||||
ReadBook.loadContent(false)
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import io.legado.app.R
|
||||
@@ -14,10 +16,7 @@ import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
import io.legado.app.lib.theme.accentColor
|
||||
import io.legado.app.model.ReadBook
|
||||
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.ui.book.read.page.entities.TextPos
|
||||
import io.legado.app.ui.book.read.page.entities.*
|
||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||
import io.legado.app.ui.book.read.page.provider.ImageProvider
|
||||
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
||||
@@ -28,7 +27,7 @@ import io.legado.app.utils.toastOnUi
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* 阅读内容界面
|
||||
* 阅读内容视图
|
||||
*/
|
||||
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
||||
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true)
|
||||
@@ -65,12 +64,18 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
callBack = activity as CallBack
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内容
|
||||
*/
|
||||
fun setContent(textPage: TextPage) {
|
||||
this.textPage = textPage
|
||||
imagePaint.isAntiAlias = AppConfig.useAntiAlias
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新绘制区域
|
||||
*/
|
||||
fun upVisibleRect() {
|
||||
visibleRect.set(
|
||||
ChapterProvider.paddingLeft.toFloat(),
|
||||
@@ -121,6 +126,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制页面
|
||||
*/
|
||||
private fun draw(
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
@@ -150,15 +158,90 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
ChapterProvider.contentPaint
|
||||
}
|
||||
val textColor = if (textLine.isReadAloud) context.accentColor else ReadBookConfig.textColor
|
||||
val linePaint = Paint()
|
||||
linePaint.strokeWidth = textPaint.textSize / 21
|
||||
linePaint.color = textColor
|
||||
val reviewCountPaint = TextPaint()
|
||||
reviewCountPaint.textSize = textPaint.textSize * 0.6F
|
||||
reviewCountPaint.color = textColor
|
||||
textLine.textChars.forEach {
|
||||
if (it.isImage) {
|
||||
drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
|
||||
} else {
|
||||
textPaint.color = textColor
|
||||
if (it.isSearchResult) {
|
||||
textPaint.color = context.accentColor
|
||||
when (it.style) {
|
||||
0 -> {
|
||||
textPaint.color = textColor
|
||||
if (it.isSearchResult) {
|
||||
textPaint.color = context.accentColor
|
||||
}
|
||||
canvas.drawText(it.charData, it.start, lineBase, textPaint)
|
||||
}
|
||||
1 -> drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
|
||||
2 -> {
|
||||
if (textLine.reviewCount <= 0) return@forEach
|
||||
canvas.drawLine(
|
||||
it.start,
|
||||
lineBase - textPaint.textSize * 2 / 5,
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase - textPaint.textSize / 4,
|
||||
linePaint
|
||||
)
|
||||
canvas.drawLine(
|
||||
it.start,
|
||||
lineBase - textPaint.textSize * 0.38F,
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase - textPaint.textSize * 0.55F,
|
||||
linePaint
|
||||
)
|
||||
canvas.drawLine(
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase - textPaint.textSize / 4,
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase,
|
||||
linePaint
|
||||
)
|
||||
canvas.drawLine(
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase - textPaint.textSize * 0.55F,
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase - textPaint.textSize * 0.8F,
|
||||
linePaint
|
||||
)
|
||||
canvas.drawLine(
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase,
|
||||
it.start + textPaint.textSize * 1.6F,
|
||||
lineBase,
|
||||
linePaint
|
||||
)
|
||||
canvas.drawLine(
|
||||
it.start + textPaint.textSize / 6,
|
||||
lineBase - textPaint.textSize * 0.8F,
|
||||
it.start + textPaint.textSize * 1.6F,
|
||||
lineBase - textPaint.textSize * 0.8F,
|
||||
linePaint
|
||||
)
|
||||
canvas.drawLine(
|
||||
it.start + textPaint.textSize * 1.6F,
|
||||
lineBase - textPaint.textSize * 0.8F,
|
||||
it.start + textPaint.textSize * 1.6F,
|
||||
lineBase,
|
||||
linePaint
|
||||
)
|
||||
if (textLine.reviewCount < 100) canvas.drawText(
|
||||
textLine.reviewCount.toString(),
|
||||
it.start + textPaint.textSize * 0.87F -
|
||||
StaticLayout.getDesiredWidth(
|
||||
textLine.reviewCount.toString(),
|
||||
reviewCountPaint
|
||||
) / 2,
|
||||
lineBase - textPaint.textSize / 6,
|
||||
reviewCountPaint
|
||||
)
|
||||
else canvas.drawText(
|
||||
"99+",
|
||||
it.start + textPaint.textSize * 0.35F,
|
||||
lineBase - textPaint.textSize / 6,
|
||||
reviewCountPaint
|
||||
)
|
||||
}
|
||||
canvas.drawText(it.charData, it.start, lineBase, textPaint)
|
||||
}
|
||||
if (it.selected) {
|
||||
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
|
||||
@@ -174,7 +257,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
textChar: TextChar,
|
||||
textChar: TextColumn,
|
||||
lineTop: Float,
|
||||
lineBottom: Float
|
||||
) {
|
||||
@@ -259,6 +342,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置滚动位置
|
||||
*/
|
||||
fun resetPageOffset() {
|
||||
pageOffset = 0
|
||||
}
|
||||
@@ -269,32 +355,49 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
fun longPress(
|
||||
x: Float,
|
||||
y: Float,
|
||||
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
||||
select: (textPos: TextPos) -> Unit,
|
||||
) {
|
||||
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar ->
|
||||
if (textChar.isImage) {
|
||||
callBack.onImageLongPress(x, y, textChar.charData)
|
||||
touch(x, y) { _, textPos, _, _, textColumn ->
|
||||
if (textColumn.style == 2) return@touch
|
||||
if (textColumn.style == 1) {
|
||||
callBack.onImageLongPress(x, y, textColumn.charData)
|
||||
} else {
|
||||
if (!selectAble) return@touch
|
||||
textChar.selected = true
|
||||
textColumn.selected = true
|
||||
invalidate()
|
||||
select(relativePos, lineIndex, charIndex)
|
||||
select(textPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单击
|
||||
* @return true:已处理, false:未处理
|
||||
*/
|
||||
fun click(x: Float, y: Float): Boolean {
|
||||
var handled = false
|
||||
touch(x, y) { _, textPos, textPage, textLine, textColumn ->
|
||||
if (textColumn.style == 2) {
|
||||
context.toastOnUi("Button Pressed!")
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
return handled
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择文字
|
||||
*/
|
||||
fun selectText(
|
||||
x: Float,
|
||||
y: Float,
|
||||
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
||||
select: (textPos: TextPos) -> Unit,
|
||||
) {
|
||||
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar ->
|
||||
textChar.selected = true
|
||||
touch(x, y) { _, textPos, _, _, textColumn ->
|
||||
if (textColumn.style == 2) return@touch
|
||||
textColumn.selected = true
|
||||
invalidate()
|
||||
select(relativePos, lineIndex, charIndex)
|
||||
select(textPos)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,11 +405,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
* 开始选择符移动
|
||||
*/
|
||||
fun selectStartMove(x: Float, y: Float) {
|
||||
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar ->
|
||||
val pos = TextPos(relativePos, lineIndex, charIndex)
|
||||
if (selectStart.compare(pos) != 0) {
|
||||
if (pos.compare(selectEnd) <= 0) {
|
||||
selectStart.upData(pos = pos)
|
||||
touch(x, y) { relativeOffset, textPos, _, textLine, textChar ->
|
||||
if (selectStart.compare(textPos) != 0) {
|
||||
if (textPos.compare(selectEnd) <= 0) {
|
||||
selectStart.upData(pos = textPos)
|
||||
upSelectedStart(
|
||||
textChar.start,
|
||||
textLine.lineBottom + relativeOffset,
|
||||
@@ -322,11 +424,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
* 结束选择符移动
|
||||
*/
|
||||
fun selectEndMove(x: Float, y: Float) {
|
||||
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar ->
|
||||
val pos = TextPos(relativePos, lineIndex, charIndex)
|
||||
if (pos.compare(selectEnd) != 0) {
|
||||
if (pos.compare(selectStart) >= 0) {
|
||||
selectEnd.upData(pos)
|
||||
touch(x, y) { relativeOffset, textPos, _, textLine, textChar ->
|
||||
if (textPos.compare(selectEnd) != 0) {
|
||||
if (textPos.compare(selectStart) >= 0) {
|
||||
selectEnd.upData(textPos)
|
||||
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset)
|
||||
upSelectChars()
|
||||
}
|
||||
@@ -334,17 +435,19 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触碰位置信息
|
||||
* @param touched 回调
|
||||
*/
|
||||
private fun touch(
|
||||
x: Float,
|
||||
y: Float,
|
||||
touched: (
|
||||
relativeOffset: Float,
|
||||
relativePos: Int,
|
||||
textPos: TextPos,
|
||||
textPage: TextPage,
|
||||
lineIndex: Int,
|
||||
textLine: TextLine,
|
||||
charIndex: Int,
|
||||
textChar: TextChar
|
||||
textColumn: TextColumn
|
||||
) -> Unit
|
||||
) {
|
||||
if (!visibleRect.contains(x, y)) return
|
||||
@@ -363,9 +466,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
if (textChar.isTouch(x)) {
|
||||
touched.invoke(
|
||||
relativeOffset,
|
||||
relativePos, textPage,
|
||||
lineIndex, textLine,
|
||||
charIndex, textChar
|
||||
TextPos(relativePos, lineIndex, charIndex),
|
||||
textPage, textLine, textChar
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -415,6 +517,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
textPos.lineIndex = lineIndex
|
||||
for ((charIndex, textChar) in textLine.textChars.withIndex()) {
|
||||
textPos.charIndex = charIndex
|
||||
if (textChar.style == 2) continue
|
||||
textChar.selected =
|
||||
textPos.compare(selectStart) >= 0 && textPos.compare(selectEnd) <= 0
|
||||
textChar.isSearchResult = textChar.selected && callBack.isSelectingSearchResult
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.legado.app.help.config.ReadTipConfig
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.book.read.ReadBookActivity
|
||||
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||
import io.legado.app.ui.book.read.page.entities.TextPos
|
||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||
import io.legado.app.ui.widget.BatteryView
|
||||
import io.legado.app.utils.activity
|
||||
@@ -24,7 +25,7 @@ import splitties.views.backgroundColor
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 阅读界面
|
||||
* 页面视图
|
||||
*/
|
||||
class PageView(context: Context) : FrameLayout(context) {
|
||||
|
||||
@@ -104,6 +105,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新阅读信息
|
||||
*/
|
||||
private fun upTipStyle() = binding.run {
|
||||
tvHeaderLeft.tag = null
|
||||
tvHeaderMiddle.tag = null
|
||||
@@ -189,6 +193,10 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信息视图
|
||||
* @param tip 信息类型
|
||||
*/
|
||||
private fun getTipView(tip: Int): BatteryView? = binding.run {
|
||||
return when (tip) {
|
||||
ReadTipConfig.tipHeaderLeft -> tvHeaderLeft
|
||||
@@ -201,6 +209,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新背景
|
||||
*/
|
||||
fun upBg() {
|
||||
if (ReadBookConfig.bgAlpha < 100) {
|
||||
binding.vwRoot.backgroundColor = ReadBookConfig.bgMeanColor
|
||||
@@ -211,15 +222,24 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
upBgAlpha()
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新背景透明度
|
||||
*/
|
||||
fun upBgAlpha() {
|
||||
binding.vwBg.alpha = ReadBookConfig.bgAlpha / 100f
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新时间信息
|
||||
*/
|
||||
fun upTime() {
|
||||
tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
|
||||
upTimeBattery()
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新电池信息
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun upBattery(battery: Int) {
|
||||
this.battery = battery
|
||||
@@ -228,6 +248,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
upTimeBattery()
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新电池信息
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun upTimeBattery() {
|
||||
val time = timeFormat.format(Date(System.currentTimeMillis()))
|
||||
@@ -235,6 +258,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
tvTimeBatteryP?.text = "$time $battery%"
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内容
|
||||
*/
|
||||
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
|
||||
setProgress(textPage)
|
||||
if (resetPageOffset) {
|
||||
@@ -243,14 +269,23 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
binding.contentTextView.setContent(textPage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置无障碍文本
|
||||
*/
|
||||
fun setContentDescription(content: String) {
|
||||
binding.contentTextView.contentDescription = content
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置滚动位置
|
||||
*/
|
||||
fun resetPageOffset() {
|
||||
binding.contentTextView.resetPageOffset()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun setProgress(textPage: TextPage) = textPage.apply {
|
||||
tvBookName?.text = ReadBook.book?.name
|
||||
@@ -260,24 +295,44 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动事件
|
||||
*/
|
||||
fun scroll(offset: Int) {
|
||||
binding.contentTextView.scroll(offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新是否开启选择功能
|
||||
*/
|
||||
fun upSelectAble(selectAble: Boolean) {
|
||||
binding.contentTextView.selectAble = selectAble
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先处理页面内单击
|
||||
* @return true:已处理, false:未处理
|
||||
*/
|
||||
fun onClick(x: Float, y: Float): Boolean {
|
||||
return binding.contentTextView.click(x, y - headerHeight)
|
||||
}
|
||||
|
||||
/**
|
||||
* 长按事件
|
||||
*/
|
||||
fun longPress(
|
||||
x: Float, y: Float,
|
||||
select: (relativePagePos: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
||||
select: (textPos: TextPos) -> Unit,
|
||||
) {
|
||||
return binding.contentTextView.longPress(x, y - headerHeight, select)
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择文本
|
||||
*/
|
||||
fun selectText(
|
||||
x: Float, y: Float,
|
||||
select: (relativePagePos: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
||||
select: (textPos: TextPos) -> Unit,
|
||||
) {
|
||||
return binding.contentTextView.selectText(x, y - headerHeight, select)
|
||||
}
|
||||
|
||||
@@ -28,11 +28,14 @@ import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
||||
import io.legado.app.utils.activity
|
||||
import io.legado.app.utils.invisible
|
||||
import io.legado.app.utils.screenshot
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import java.text.BreakIterator
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
/**
|
||||
* 阅读视图
|
||||
*/
|
||||
class ReadView(context: Context, attrs: AttributeSet) :
|
||||
FrameLayout(context, attrs),
|
||||
DataSource {
|
||||
@@ -222,7 +225,9 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
pressDown = false
|
||||
if (!isPageMove) {
|
||||
if (!longPressed && !pressOnTextSelected) {
|
||||
onSingleTapUp()
|
||||
if (!curPage.onClick(startX, startY)) {
|
||||
onSingleTapUp()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -292,18 +297,18 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
*/
|
||||
private fun onLongPress() {
|
||||
kotlin.runCatching {
|
||||
curPage.longPress(startX, startY) { relativePos, lineIndex, charIndex ->
|
||||
curPage.longPress(startX, startY) { textPos: TextPos ->
|
||||
isTextSelected = true
|
||||
pressOnTextSelected = true
|
||||
initialTextPos.upData(relativePos, lineIndex, charIndex)
|
||||
val startPos = TextPos(relativePos, lineIndex, charIndex)
|
||||
val endPos = TextPos(relativePos, lineIndex, charIndex)
|
||||
val page = curPage.relativePage(relativePos)
|
||||
initialTextPos.upData(textPos)
|
||||
val startPos = textPos.copy()
|
||||
val endPos = textPos.copy()
|
||||
val page = curPage.relativePage(textPos.relativePagePos)
|
||||
val stringBuilder = StringBuilder()
|
||||
var cIndex = charIndex
|
||||
var lineStart = lineIndex
|
||||
var lineEnd = lineIndex
|
||||
for (index in lineIndex - 1 downTo 0) {
|
||||
var cIndex = textPos.charIndex
|
||||
var lineStart = textPos.lineIndex
|
||||
var lineEnd = textPos.lineIndex
|
||||
for (index in textPos.lineIndex - 1 downTo 0) {
|
||||
val textLine = page.getLine(index)
|
||||
if (textLine.isParagraphEnd) {
|
||||
break
|
||||
@@ -313,7 +318,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
cIndex += textLine.charSize
|
||||
}
|
||||
}
|
||||
for (index in lineIndex until page.lineSize) {
|
||||
for (index in textPos.lineIndex until page.lineSize) {
|
||||
val textLine = page.getLine(index)
|
||||
stringBuilder.append(textLine.text)
|
||||
lineEnd += 1
|
||||
@@ -419,11 +424,15 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
* 选择文本
|
||||
*/
|
||||
private fun selectText(x: Float, y: Float) {
|
||||
curPage.selectText(x, y) { relativePagePos, lineIndex, charIndex ->
|
||||
val compare = initialTextPos.compare(relativePagePos, lineIndex, charIndex)
|
||||
curPage.selectText(x, y) { textPos ->
|
||||
val compare = initialTextPos.compare(textPos)
|
||||
when {
|
||||
compare >= 0 -> {
|
||||
curPage.selectStartMoveIndex(relativePagePos, lineIndex, charIndex)
|
||||
curPage.selectStartMoveIndex(
|
||||
textPos.relativePagePos,
|
||||
textPos.lineIndex,
|
||||
textPos.charIndex
|
||||
)
|
||||
curPage.selectEndMoveIndex(
|
||||
initialTextPos.relativePagePos,
|
||||
initialTextPos.lineIndex,
|
||||
@@ -436,7 +445,11 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
initialTextPos.lineIndex,
|
||||
initialTextPos.charIndex
|
||||
)
|
||||
curPage.selectEndMoveIndex(relativePagePos, lineIndex, charIndex)
|
||||
curPage.selectEndMoveIndex(
|
||||
textPos.relativePagePos,
|
||||
textPos.lineIndex,
|
||||
textPos.charIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,11 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
|
||||
abortAnim()
|
||||
if (!hasNext()) return
|
||||
setDirection(PageDirection.NEXT)
|
||||
readView.setStartPoint(viewWidth.toFloat(), 1f, false)
|
||||
val y = when {
|
||||
viewHeight / 2 < startY -> viewHeight.toFloat() * 0.9f
|
||||
else -> 1f
|
||||
}
|
||||
readView.setStartPoint(viewWidth.toFloat() * 0.9f, y, false)
|
||||
onAnimStart(animationSpeed)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package io.legado.app.ui.book.read.page.entities
|
||||
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* 章节信息
|
||||
*/
|
||||
@Suppress("unused")
|
||||
data class TextChapter(
|
||||
val position: Int,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package io.legado.app.ui.book.read.page.entities
|
||||
|
||||
data class TextChar(
|
||||
/**
|
||||
* 字符信息
|
||||
*/
|
||||
data class TextColumn(
|
||||
val charData: String,
|
||||
var start: Float,
|
||||
var end: Float,
|
||||
val style: Int = 0, //0:文字,1:图片,2:按钮
|
||||
var selected: Boolean = false,
|
||||
var isImage: Boolean = false,
|
||||
var isSearchResult: Boolean = false
|
||||
) {
|
||||
|
||||
@@ -4,10 +4,14 @@ import android.text.TextPaint
|
||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||
import io.legado.app.utils.textHeight
|
||||
|
||||
/**
|
||||
* 行信息
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
data class TextLine(
|
||||
var text: String = "",
|
||||
val textChars: ArrayList<TextChar> = arrayListOf(),
|
||||
val textChars: ArrayList<TextColumn> = arrayListOf(),
|
||||
val reviewCount: Int = 0,
|
||||
var lineTop: Float = 0f,
|
||||
var lineBase: Float = 0f,
|
||||
var lineBottom: Float = 0f,
|
||||
@@ -27,13 +31,13 @@ data class TextLine(
|
||||
lineBase = lineBottom - textPaint.fontMetrics.descent
|
||||
}
|
||||
|
||||
fun getTextChar(index: Int): TextChar {
|
||||
fun getTextChar(index: Int): TextColumn {
|
||||
return textChars.getOrElse(index) {
|
||||
textChars.last()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTextCharReverseAt(index: Int): TextChar {
|
||||
fun getTextCharReverseAt(index: Int): TextColumn {
|
||||
return textChars[textChars.lastIndex - index]
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ import splitties.init.appCtx
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* 页面信息
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
data class TextPage(
|
||||
var index: Int = 0,
|
||||
@@ -111,7 +114,12 @@ data class TextPage(
|
||||
val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint)
|
||||
val x1 = x + cw
|
||||
textLine.textChars.add(
|
||||
TextChar(char, start = x, end = x1)
|
||||
TextColumn(
|
||||
char,
|
||||
start = x,
|
||||
end = x1,
|
||||
style = if (textLine.text.length - 1 == index && char == "\uD83D\uDCAC") 2 else 0
|
||||
)
|
||||
)
|
||||
x = x1
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package io.legado.app.ui.book.read.page.entities
|
||||
|
||||
/**
|
||||
* 位置信息
|
||||
*/
|
||||
data class TextPos(
|
||||
var relativePagePos: Int,
|
||||
var lineIndex: Int,
|
||||
|
||||
@@ -14,7 +14,7 @@ import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
import io.legado.app.model.ReadBook
|
||||
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.TextColumn
|
||||
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.*
|
||||
@@ -116,7 +116,13 @@ object ChapterProvider {
|
||||
if (ReadBookConfig.titleMode != 2) {
|
||||
displayTitle.splitNotBlank("\n").forEach { text ->
|
||||
setTypeText(
|
||||
book, absStartX, durY, text, textPages, stringBuilder, titlePaint,
|
||||
book,
|
||||
absStartX,
|
||||
durY,
|
||||
if (AppConfig.enableReview) text + "\ud83d\udcac" else text,
|
||||
textPages,
|
||||
stringBuilder,
|
||||
titlePaint,
|
||||
isTitle = true,
|
||||
isTitleWithNoContent = contents.isEmpty(),
|
||||
isVolumeTitle = bookChapter.isVolume
|
||||
@@ -171,7 +177,13 @@ object ChapterProvider {
|
||||
val text = content.substring(start, content.length)
|
||||
if (text.isNotBlank()) {
|
||||
setTypeText(
|
||||
book, absStartX, durY, text, textPages, stringBuilder, contentPaint
|
||||
book,
|
||||
absStartX,
|
||||
durY,
|
||||
if (AppConfig.enableReview) text + "\ud83d\udcac" else text,
|
||||
textPages,
|
||||
stringBuilder,
|
||||
contentPaint
|
||||
).let {
|
||||
absStartX = it.first
|
||||
durY = it.second
|
||||
@@ -252,7 +264,7 @@ object ChapterProvider {
|
||||
Pair(0f, width.toFloat())
|
||||
}
|
||||
textLine.textChars.add(
|
||||
TextChar(charData = src, start = x + start, end = x + end, isImage = true)
|
||||
TextColumn(charData = src, start = x + start, end = x + end, style = 1)
|
||||
)
|
||||
textPages.last().textLines.add(textLine)
|
||||
}
|
||||
@@ -276,9 +288,8 @@ object ChapterProvider {
|
||||
srcList: LinkedList<String>? = null
|
||||
): Pair<Int, Float> {
|
||||
var absStartX = x
|
||||
val layout = if (ReadBookConfig.useZhLayout) {
|
||||
ZhLayout(text, textPaint, visibleWidth)
|
||||
} else StaticLayout(
|
||||
val layout = if (ReadBookConfig.useZhLayout) ZhLayout(text, textPaint, visibleWidth)
|
||||
else StaticLayout(
|
||||
text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true
|
||||
)
|
||||
var durY = when {
|
||||
@@ -398,10 +409,14 @@ object ChapterProvider {
|
||||
}
|
||||
val bodyIndent = ReadBookConfig.paragraphIndent
|
||||
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
|
||||
bodyIndent.toStringArray().forEach { char ->
|
||||
for (char in bodyIndent.toStringArray()) {
|
||||
val x1 = x + icw
|
||||
textLine.textChars.add(
|
||||
TextChar(charData = char, start = absStartX + x, end = absStartX + x1)
|
||||
TextColumn(
|
||||
charData = char,
|
||||
start = absStartX + x,
|
||||
end = absStartX + x1
|
||||
)
|
||||
)
|
||||
x = x1
|
||||
}
|
||||
@@ -436,7 +451,7 @@ object ChapterProvider {
|
||||
words.forEachIndexed { index, char ->
|
||||
val cw = StaticLayout.getDesiredWidth(char, textPaint)
|
||||
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
||||
addCharToLine(book, absStartX, textLine, char, x, x1, srcList)
|
||||
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
|
||||
x = x1
|
||||
}
|
||||
exceed(absStartX, textLine, words)
|
||||
@@ -455,10 +470,10 @@ object ChapterProvider {
|
||||
srcList: LinkedList<String>?
|
||||
) {
|
||||
var x = startX
|
||||
words.forEach { char ->
|
||||
words.forEachIndexed { index, char ->
|
||||
val cw = StaticLayout.getDesiredWidth(char, textPaint)
|
||||
val x1 = x + cw
|
||||
addCharToLine(book, absStartX, textLine, char, x, x1, srcList)
|
||||
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
|
||||
x = x1
|
||||
}
|
||||
exceed(absStartX, textLine, words)
|
||||
@@ -474,25 +489,27 @@ object ChapterProvider {
|
||||
char: String,
|
||||
xStart: Float,
|
||||
xEnd: Float,
|
||||
isLineEnd: Boolean,
|
||||
srcList: LinkedList<String>?
|
||||
) {
|
||||
if (srcList != null && char == srcReplaceChar) {
|
||||
val src = srcList.removeFirst()
|
||||
ImageProvider.cacheImage(book, src, ReadBook.bookSource)
|
||||
textLine.textChars.add(
|
||||
TextChar(
|
||||
TextColumn(
|
||||
charData = src,
|
||||
start = absStartX + xStart,
|
||||
end = absStartX + xEnd,
|
||||
isImage = true
|
||||
style = 1
|
||||
)
|
||||
)
|
||||
} else {
|
||||
textLine.textChars.add(
|
||||
TextChar(
|
||||
TextColumn(
|
||||
charData = char,
|
||||
start = absStartX + xStart,
|
||||
end = absStartX + xEnd
|
||||
end = absStartX + xEnd,
|
||||
style = if (isLineEnd && char == "\uD83D\uDCAC") 2 else 0
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -612,12 +629,12 @@ object ChapterProvider {
|
||||
* 更新绘制尺寸
|
||||
*/
|
||||
fun upLayout() {
|
||||
when(AppConfig.doublePageHorizontal){
|
||||
when (AppConfig.doublePageHorizontal) {
|
||||
"0" -> doublePage = false
|
||||
"1" -> doublePage = true
|
||||
"2" -> {
|
||||
doublePage = (viewWidth > viewHeight)
|
||||
&& ReadBook.pageAnim() != 3
|
||||
&& ReadBook.pageAnim() != 3
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,10 +41,11 @@ class BookSourceEditActivity :
|
||||
private val adapter by lazy { BookSourceEditAdapter() }
|
||||
private val sourceEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val searchEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val findEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val exploreEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val infoEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val tocEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val contentEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val reviewEntities: ArrayList<EditEntity> = ArrayList()
|
||||
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
|
||||
it ?: return@registerForActivityResult
|
||||
viewModel.importSource(it) { source ->
|
||||
@@ -180,10 +181,11 @@ class BookSourceEditActivity :
|
||||
private fun setEditEntities(tabPosition: Int?) {
|
||||
when (tabPosition) {
|
||||
1 -> adapter.editEntities = searchEntities
|
||||
2 -> adapter.editEntities = findEntities
|
||||
2 -> adapter.editEntities = exploreEntities
|
||||
3 -> adapter.editEntities = infoEntities
|
||||
4 -> adapter.editEntities = tocEntities
|
||||
5 -> adapter.editEntities = contentEntities
|
||||
6 -> adapter.editEntities = reviewEntities
|
||||
else -> adapter.editEntities = sourceEntities
|
||||
}
|
||||
binding.recyclerView.scrollToPosition(0)
|
||||
@@ -192,8 +194,9 @@ class BookSourceEditActivity :
|
||||
private fun upSourceView(source: BookSource? = viewModel.bookSource) {
|
||||
source?.let {
|
||||
binding.cbIsEnable.isChecked = it.enabled
|
||||
binding.cbIsEnableFind.isChecked = it.enabledExplore
|
||||
binding.cbIsEnableExplore.isChecked = it.enabledExplore
|
||||
binding.cbIsEnableCookie.isChecked = it.enabledCookieJar ?: false
|
||||
binding.cbIsEnableReview.isChecked = it.enabledReview ?: false
|
||||
binding.spType.setSelection(
|
||||
when (it.bookSourceType) {
|
||||
BookType.file -> 3
|
||||
@@ -203,7 +206,7 @@ class BookSourceEditActivity :
|
||||
}
|
||||
)
|
||||
}
|
||||
//基本信息
|
||||
// 基本信息
|
||||
sourceEntities.clear()
|
||||
sourceEntities.apply {
|
||||
add(EditEntity("bookSourceUrl", source?.bookSourceUrl, R.string.source_url))
|
||||
@@ -218,7 +221,7 @@ class BookSourceEditActivity :
|
||||
add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment))
|
||||
add(EditEntity("concurrentRate", source?.concurrentRate, R.string.concurrent_rate))
|
||||
}
|
||||
//搜索
|
||||
// 搜索
|
||||
val sr = source?.getSearchRule()
|
||||
searchEntities.clear()
|
||||
searchEntities.apply {
|
||||
@@ -234,10 +237,10 @@ class BookSourceEditActivity :
|
||||
add(EditEntity("coverUrl", sr?.coverUrl, R.string.rule_cover_url))
|
||||
add(EditEntity("bookUrl", sr?.bookUrl, R.string.r_book_url))
|
||||
}
|
||||
//发现
|
||||
// 发现
|
||||
val er = source?.getExploreRule()
|
||||
findEntities.clear()
|
||||
findEntities.apply {
|
||||
exploreEntities.clear()
|
||||
exploreEntities.apply {
|
||||
add(EditEntity("exploreUrl", source?.exploreUrl, R.string.r_find_url))
|
||||
add(EditEntity("bookList", er?.bookList, R.string.r_book_list))
|
||||
add(EditEntity("name", er?.name, R.string.r_book_name))
|
||||
@@ -249,7 +252,7 @@ class BookSourceEditActivity :
|
||||
add(EditEntity("coverUrl", er?.coverUrl, R.string.rule_cover_url))
|
||||
add(EditEntity("bookUrl", er?.bookUrl, R.string.r_book_url))
|
||||
}
|
||||
//详情页
|
||||
// 详情页
|
||||
val ir = source?.getBookInfoRule()
|
||||
infoEntities.clear()
|
||||
infoEntities.apply {
|
||||
@@ -265,7 +268,7 @@ class BookSourceEditActivity :
|
||||
add(EditEntity("canReName", ir?.canReName, R.string.rule_can_re_name))
|
||||
add(EditEntity("downloadUrls", ir?.downloadUrls, R.string.download_url_rule))
|
||||
}
|
||||
//目录页
|
||||
// 目录页
|
||||
val tr = source?.getTocRule()
|
||||
tocEntities.clear()
|
||||
tocEntities.apply {
|
||||
@@ -279,7 +282,7 @@ class BookSourceEditActivity :
|
||||
add(EditEntity("isPay", tr?.isPay, R.string.rule_is_pay))
|
||||
add(EditEntity("nextTocUrl", tr?.nextTocUrl, R.string.rule_next_toc_url))
|
||||
}
|
||||
//正文页
|
||||
// 正文页
|
||||
val cr = source?.getContentRule()
|
||||
contentEntities.clear()
|
||||
contentEntities.apply {
|
||||
@@ -291,6 +294,21 @@ class BookSourceEditActivity :
|
||||
add(EditEntity("imageStyle", cr?.imageStyle, R.string.rule_image_style))
|
||||
add(EditEntity("payAction", cr?.payAction, R.string.rule_pay_action))
|
||||
}
|
||||
// 段评
|
||||
val rr = source?.getReviewRule()
|
||||
reviewEntities.clear()
|
||||
reviewEntities.apply {
|
||||
add(EditEntity("reviewUrl", rr?.reviewUrl, R.string.rule_review_url))
|
||||
add(EditEntity("avatarRule", rr?.avatarRule, R.string.rule_avatar))
|
||||
add(EditEntity("contentRule", rr?.contentRule, R.string.rule_review_content))
|
||||
add(EditEntity("postTimeRule", rr?.postTimeRule, R.string.rule_post_time))
|
||||
add(EditEntity("reviewQuoteUrl", rr?.reviewQuoteUrl, R.string.rule_review_quote))
|
||||
add(EditEntity("voteUpUrl", rr?.voteUpUrl, R.string.review_vote_up))
|
||||
add(EditEntity("voteDownUrl", rr?.voteDownUrl, R.string.review_vote_down))
|
||||
add(EditEntity("postReviewUrl", rr?.postReviewUrl, R.string.post_review_url))
|
||||
add(EditEntity("postQuoteUrl", rr?.postQuoteUrl, R.string.post_quote_url))
|
||||
add(EditEntity("deleteUrl", rr?.deleteUrl, R.string.delete_review_url))
|
||||
}
|
||||
binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0))
|
||||
setEditEntities(0)
|
||||
}
|
||||
@@ -298,8 +316,9 @@ class BookSourceEditActivity :
|
||||
private fun getSource(): BookSource {
|
||||
val source = viewModel.bookSource?.copy() ?: BookSource()
|
||||
source.enabled = binding.cbIsEnable.isChecked
|
||||
source.enabledExplore = binding.cbIsEnableFind.isChecked
|
||||
source.enabledExplore = binding.cbIsEnableExplore.isChecked
|
||||
source.enabledCookieJar = binding.cbIsEnableCookie.isChecked
|
||||
source.enabledReview = binding.cbIsEnableReview.isChecked
|
||||
source.bookSourceType = when (binding.spType.selectedItemPosition) {
|
||||
3 -> BookType.file
|
||||
2 -> BookType.image
|
||||
@@ -311,6 +330,7 @@ class BookSourceEditActivity :
|
||||
val bookInfoRule = BookInfoRule()
|
||||
val tocRule = TocRule()
|
||||
val contentRule = ContentRule()
|
||||
val reviewRule = ReviewRule()
|
||||
sourceEntities.forEach {
|
||||
when (it.key) {
|
||||
"bookSourceUrl" -> source.bookSourceUrl = it.value ?: ""
|
||||
@@ -351,7 +371,7 @@ class BookSourceEditActivity :
|
||||
viewModel.ruleComplete(it.value, searchRule.bookList, 2)
|
||||
}
|
||||
}
|
||||
findEntities.forEach {
|
||||
exploreEntities.forEach {
|
||||
when (it.key) {
|
||||
"exploreUrl" -> source.exploreUrl = it.value
|
||||
"bookList" -> exploreRule.bookList = it.value
|
||||
@@ -429,11 +449,30 @@ class BookSourceEditActivity :
|
||||
"payAction" -> contentRule.payAction = it.value
|
||||
}
|
||||
}
|
||||
reviewEntities.forEach {
|
||||
when (it.key) {
|
||||
"reviewUrl" -> reviewRule.reviewUrl = it.value
|
||||
"avatarRule" -> reviewRule.avatarRule =
|
||||
viewModel.ruleComplete(it.value, reviewRule.reviewUrl, 3)
|
||||
"contentRule" -> reviewRule.contentRule =
|
||||
viewModel.ruleComplete(it.value, reviewRule.reviewUrl)
|
||||
"postTimeRule" -> reviewRule.postTimeRule =
|
||||
viewModel.ruleComplete(it.value, reviewRule.reviewUrl)
|
||||
"reviewQuoteUrl" -> reviewRule.reviewQuoteUrl =
|
||||
viewModel.ruleComplete(it.value, reviewRule.reviewUrl, 2)
|
||||
"voteUpUrl" -> reviewRule.voteUpUrl = it.value
|
||||
"voteDownUrl" -> reviewRule.voteDownUrl = it.value
|
||||
"postReviewUrl" -> reviewRule.postReviewUrl = it.value
|
||||
"postQuoteUrl" -> reviewRule.postQuoteUrl = it.value
|
||||
"deleteUrl" -> reviewRule.deleteUrl =it.value
|
||||
}
|
||||
}
|
||||
source.ruleSearch = searchRule
|
||||
source.ruleExplore = exploreRule
|
||||
source.ruleBookInfo = bookInfoRule
|
||||
source.ruleToc = tocRule
|
||||
source.ruleContent = contentRule
|
||||
source.ruleReview = reviewRule
|
||||
return source
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
android:text="@string/is_enable" />
|
||||
|
||||
<io.legado.app.lib.theme.view.ThemeCheckBox
|
||||
android:id="@+id/cb_is_enable_find"
|
||||
android:id="@+id/cb_is_enable_explore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
@@ -40,18 +40,33 @@
|
||||
android:checked="true"
|
||||
android:text="@string/auto_save_cookie" />
|
||||
|
||||
<TextView
|
||||
<io.legado.app.lib.theme.view.ThemeCheckBox
|
||||
android:id="@+id/cb_is_enable_review"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/review" />
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:height="30dp"
|
||||
android:gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/book_type"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="3dp"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/sp_type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:entries="@array/book_type"
|
||||
app:theme="@style/Spinner" />
|
||||
|
||||
@@ -94,6 +109,11 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/source_tab_content" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/review" />
|
||||
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
@@ -84,6 +84,13 @@
|
||||
android:checked="false"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_enable_review"
|
||||
android:title="@string/review"
|
||||
android:checkable="true"
|
||||
android:checked="false"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_image_style"
|
||||
android:title="@string/image_style"
|
||||
|
||||
@@ -1010,6 +1010,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">Review</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">show read title addition area</string>
|
||||
|
||||
@@ -1013,6 +1013,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">Review</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">show read title addition area</string>
|
||||
|
||||
@@ -1013,6 +1013,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">Review</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">show read title addition area</string>
|
||||
|
||||
@@ -1010,6 +1010,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">段评</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">展示顶部工具栏附加区域</string>
|
||||
|
||||
@@ -1012,6 +1012,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">段评</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">展示顶部工具栏附加区域</string>
|
||||
|
||||
@@ -1012,6 +1012,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">段评</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">展示顶部工具栏附加区域</string>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="pk_bookshelf_px">bookshelf_px</string>
|
||||
<string name="legado_gzh">开源阅读</string>
|
||||
<string name="email">kunfei.ge@gmail.com</string>
|
||||
<string name="pk_bookshelf_px" translatable="false">bookshelf_px</string>
|
||||
<string name="legado_gzh" translatable="false">开源阅读</string>
|
||||
<string name="email" translatable="false">kunfei.ge@gmail.com</string>
|
||||
|
||||
<string name="source_rule_url">https://alanskycn.gitee.io/teachme/</string>
|
||||
<string name="this_github_url">https://github.com/gedoor/legado</string>
|
||||
<string name="contributors_url">https://github.com/gedoor/legado/graphs/contributors</string>
|
||||
<string name="home_page_url">https://gedoor.github.io</string>
|
||||
<string name="license_url">https://github.com/gedoor/legado/blob/master/LICENSE</string>
|
||||
<string name="latest_release_url">https://github.com/gedoor/legado/releases/latest</string>
|
||||
<string name="latest_release_api">https://api.github.com/repos/gedoor/legado/releases/latest</string>
|
||||
<string name="tg_url">https://t.me/legado_channels</string>
|
||||
<string name="discord_url">https://discord.gg/qDE52P5xGW</string>
|
||||
<string name="source_rule_url" translatable="false">https://alanskycn.gitee.io/teachme/</string>
|
||||
<string name="this_github_url" translatable="false">https://github.com/gedoor/legado</string>
|
||||
<string name="contributors_url" translatable="false">https://github.com/gedoor/legado/graphs/contributors</string>
|
||||
<string name="home_page_url" translatable="false">https://gedoor.github.io</string>
|
||||
<string name="license_url" translatable="false">https://github.com/gedoor/legado/blob/master/LICENSE</string>
|
||||
<string name="latest_release_url" translatable="false">https://github.com/gedoor/legado/releases/latest</string>
|
||||
<string name="latest_release_api" translatable="false">https://api.github.com/repos/gedoor/legado/releases/latest</string>
|
||||
<string name="tg_url" translatable="false">https://t.me/legado_channels</string>
|
||||
<string name="discord_url" translatable="false">https://discord.gg/qDE52P5xGW</string>
|
||||
|
||||
<string name="http_ip">http://%1$s:%2$d</string>
|
||||
<string name="git_hub">GitHub</string>
|
||||
<string name="diy_edit_source_group_title">【%s】</string>
|
||||
<string name="vip_title">🔒%s</string>
|
||||
<string name="payed_title">🔓%s</string>
|
||||
<string name="http_ip" translatable="false">http://%1$s:%2$d</string>
|
||||
<string name="git_hub" translatable="false">GitHub</string>
|
||||
<string name="diy_edit_source_group_title" translatable="false">【%s】</string>
|
||||
<string name="vip_title" translatable="false">🔒%s</string>
|
||||
<string name="payed_title" translatable="false">🔓%s</string>
|
||||
|
||||
<string name="separator">丨</string>
|
||||
<string name="separator" translatable="false">丨</string>
|
||||
|
||||
</resources>
|
||||
@@ -1013,6 +1013,17 @@
|
||||
<string name="check_selected_interval">选中所选区间</string>
|
||||
<string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
|
||||
<string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
|
||||
<string name="review">Review</string>
|
||||
<string name="rule_avatar">段评发布者头像(avatarRule)</string>
|
||||
<string name="rule_review_url">段评URL(reviewUrl)</string>
|
||||
<string name="rule_review_content">段评内容(contentRule)</string>
|
||||
<string name="rule_post_time">段评发布时间(postTimeRule)</string>
|
||||
<string name="rule_review_quote">段评回复URL(reviewQuoteUrl)</string>
|
||||
<string name="review_vote_down">点踩URL(voteUpUrl)</string>
|
||||
<string name="review_vote_up">点赞URL(voteDownUrl)</string>
|
||||
<string name="post_review_url">发送回复URL(postReviewUrl)</string>
|
||||
<string name="post_quote_url">发送回复段评URL(postQuoteUrl)</string>
|
||||
<string name="delete_review_url">删除段评URL(deleteUrl)</string>
|
||||
<string name="tag_explore_enabled">标志:发现已启用</string>
|
||||
<string name="tag_explore_disabled">标志:发现已禁用</string>
|
||||
<string name="show_read_title_addition">show read title addition area</string>
|
||||
|
||||
Reference in New Issue
Block a user