合并上游更改
感谢:@821938089、@zhongerxll、@youke2580c、@Leroy-X、@Dark-Tide、@lhjgege、@3harmony、@mgz0227
This commit is contained in:
87 files changed
+646
-211
No files matched your search
@@ -45,7 +45,7 @@
|
|||||||
* 调用阅读搜索
|
* 调用阅读搜索
|
||||||
|
|
||||||
```js
|
```js
|
||||||
java.searchBook(bookName: string)
|
java.searchBook(bookName: String)
|
||||||
```
|
```
|
||||||
|
|
||||||
* 添加书架
|
* 添加书架
|
||||||
@@ -232,6 +232,13 @@ java.s2t(text: String): String
|
|||||||
* 时间格式化
|
* 时间格式化
|
||||||
```js
|
```js
|
||||||
java.timeFormatUTC(time: Long, format: String, sh: Int): String?
|
java.timeFormatUTC(time: Long, format: String, sh: Int): String?
|
||||||
|
java.timeFormat(time: Long): String
|
||||||
|
```
|
||||||
|
|
||||||
|
* html格式化
|
||||||
|
|
||||||
|
```js
|
||||||
|
java.htmlFormat(str: String): String
|
||||||
```
|
```
|
||||||
* 文件
|
* 文件
|
||||||
> 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存/android/data/{package}/cache/内的文件
|
> 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存/android/data/{package}/cache/内的文件
|
||||||
@@ -441,9 +448,11 @@ cache.putFile(key: String, value: String, saveTime: Int)
|
|||||||
读取文件内容
|
读取文件内容
|
||||||
cache.getFile(key: String): String?
|
cache.getFile(key: String): String?
|
||||||
保存到内存
|
保存到内存
|
||||||
cache.deleteMemory(key: String)
|
|
||||||
cache.getFromMemory(key: String): Any?
|
|
||||||
cache.putMemory(key: String, value: Any)
|
cache.putMemory(key: String, value: Any)
|
||||||
|
读取内存
|
||||||
|
cache.getFromMemory(key: String): Any?
|
||||||
|
删除内存
|
||||||
|
cache.deleteMemory(key: String)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.app.NotificationManager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.os.Build
|
||||||
import com.github.liuyueyi.quick.transfer.constants.TransType
|
import com.github.liuyueyi.quick.transfer.constants.TransType
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||||
@@ -141,6 +142,9 @@ class App : Application() {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private fun installGmsTlsProvider(context: Context) {
|
private fun installGmsTlsProvider(context: Context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val gmsPackageName = "com.google.android.gms"
|
val gmsPackageName = "com.google.android.gms"
|
||||||
val appInfo = packageManager.getApplicationInfo(gmsPackageName, 0)
|
val appInfo = packageManager.getApplicationInfo(gmsPackageName, 0)
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ abstract class BaseService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onTimeout(startId: Int) {
|
override fun onTimeout(startId: Int, fgsType: Int) {
|
||||||
super.onTimeout(startId)
|
super.onTimeout(startId, fgsType)
|
||||||
LogUtils.d(simpleName, "onTimeout startId:$startId")
|
LogUtils.d(simpleName, "onTimeout startId:$startId fgsType:$fgsType")
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ object AppPattern {
|
|||||||
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
|
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
|
||||||
|
|
||||||
//匹配格式化后的图片格式
|
//匹配格式化后的图片格式
|
||||||
val imgPattern: Pattern = Pattern.compile("<img[^>]*src=\"([^\"]*(?:\"[^>]+\\})?)\"[^>]*>")
|
val imgPattern: Pattern =
|
||||||
|
Pattern.compile("<img[^>]*src=['\"]([^'\"]*(?:['\"][^>]+\\})?)['\"][^>]*>")
|
||||||
|
|
||||||
//dataURL图片类型
|
//dataURL图片类型
|
||||||
val dataUriRegex = Regex("^data:.*?;base64,(.*)")
|
val dataUriRegex = Regex("^data:.*?;base64,(.*)")
|
||||||
|
|||||||
@@ -32,4 +32,5 @@ object EventBus {
|
|||||||
const val READ_ALOUD_PLAY = "readAloudPlay"
|
const val READ_ALOUD_PLAY = "readAloudPlay"
|
||||||
const val EXPORT_BOOK = "exportBook"
|
const val EXPORT_BOOK = "exportBook"
|
||||||
const val UP_MANGA_CONFIG = "upMangaConfig"
|
const val UP_MANGA_CONFIG = "upMangaConfig"
|
||||||
|
const val PLAY_MODE_CHANGED = "playModeChanged"
|
||||||
}
|
}
|
||||||
@@ -170,14 +170,12 @@ object PreferKey {
|
|||||||
const val showMangaUi = "showMangaUi"
|
const val showMangaUi = "showMangaUi"
|
||||||
const val disableMangaScale = "disableMangaScale"
|
const val disableMangaScale = "disableMangaScale"
|
||||||
const val firebaseEnabled = "firebaseEnabled"
|
const val firebaseEnabled = "firebaseEnabled"
|
||||||
|
|
||||||
const val cPrimary = "colorPrimary"
|
const val cPrimary = "colorPrimary"
|
||||||
const val cAccent = "colorAccent"
|
const val cAccent = "colorAccent"
|
||||||
const val cBackground = "colorBackground"
|
const val cBackground = "colorBackground"
|
||||||
const val cBBackground = "colorBottomBackground"
|
const val cBBackground = "colorBottomBackground"
|
||||||
const val bgImage = "backgroundImage"
|
const val bgImage = "backgroundImage"
|
||||||
const val bgImageBlurring = "backgroundImageBlurring"
|
const val bgImageBlurring = "backgroundImageBlurring"
|
||||||
|
|
||||||
const val cNPrimary = "colorPrimaryNight"
|
const val cNPrimary = "colorPrimaryNight"
|
||||||
const val cNAccent = "colorAccentNight"
|
const val cNAccent = "colorAccentNight"
|
||||||
const val cNBackground = "colorBackgroundNight"
|
const val cNBackground = "colorBackgroundNight"
|
||||||
@@ -187,7 +185,6 @@ object PreferKey {
|
|||||||
const val showReadTitleAddition = "showReadTitleAddition"
|
const val showReadTitleAddition = "showReadTitleAddition"
|
||||||
const val readBarStyleFollowPage = "readBarStyleFollowPage"
|
const val readBarStyleFollowPage = "readBarStyleFollowPage"
|
||||||
const val contentSelectSpeakMod = "contentReadAloudMod"
|
const val contentSelectSpeakMod = "contentReadAloudMod"
|
||||||
|
|
||||||
const val themePref = "themePref"
|
const val themePref = "themePref"
|
||||||
const val bookshelfSortOrder = "bookshelfSortOrder"
|
const val bookshelfSortOrder = "bookshelfSortOrder"
|
||||||
const val showBottomView = "showBottomView"
|
const val showBottomView = "showBottomView"
|
||||||
@@ -208,5 +205,5 @@ object PreferKey {
|
|||||||
const val permissionChecked = "permissionChecked"
|
const val permissionChecked = "permissionChecked"
|
||||||
const val notificationsPost = "notificationsPost"
|
const val notificationsPost = "notificationsPost"
|
||||||
const val ignoreBatteryPermission = "ignoreBatteryPermission"
|
const val ignoreBatteryPermission = "ignoreBatteryPermission"
|
||||||
|
const val paddingDisplayCutouts = "paddingDisplayCutouts"
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import androidx.room.Delete
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import io.legato.kazusa.constant.BookType
|
import io.legato.kazusa.constant.BookType
|
||||||
import io.legato.kazusa.data.entities.Book
|
import io.legato.kazusa.data.entities.Book
|
||||||
@@ -150,6 +151,12 @@ interface BookDao {
|
|||||||
@Delete
|
@Delete
|
||||||
fun delete(vararg book: Book)
|
fun delete(vararg book: Book)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
fun replace(oldBook: Book, newBook: Book) {
|
||||||
|
delete(oldBook)
|
||||||
|
insert(newBook)
|
||||||
|
}
|
||||||
|
|
||||||
@Query("update books set durChapterPos = :pos where bookUrl = :bookUrl")
|
@Query("update books set durChapterPos = :pos where bookUrl = :bookUrl")
|
||||||
fun upProgress(bookUrl: String, pos: Int)
|
fun upProgress(bookUrl: String, pos: Int)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import io.legato.kazusa.help.config.AppConfig
|
|||||||
import io.legato.kazusa.help.crypto.SymmetricCryptoAndroid
|
import io.legato.kazusa.help.crypto.SymmetricCryptoAndroid
|
||||||
import io.legato.kazusa.help.http.CookieStore
|
import io.legato.kazusa.help.http.CookieStore
|
||||||
import io.legato.kazusa.help.source.getShareScope
|
import io.legato.kazusa.help.source.getShareScope
|
||||||
import io.legato.kazusa.model.Debug
|
|
||||||
import io.legato.kazusa.utils.GSON
|
import io.legato.kazusa.utils.GSON
|
||||||
import io.legato.kazusa.utils.GSONStrict
|
import io.legato.kazusa.utils.GSONStrict
|
||||||
import io.legato.kazusa.utils.fromJsonArray
|
import io.legato.kazusa.utils.fromJsonArray
|
||||||
@@ -116,7 +115,7 @@ interface BaseSource : JsExtensions {
|
|||||||
GSONStrict.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->
|
GSONStrict.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->
|
||||||
putAll(map)
|
putAll(map)
|
||||||
} ?: GSON.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->
|
} ?: GSON.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->
|
||||||
Debug.log("≡请求头规则 JSON 格式不规范,请改为规范格式")
|
log("请求头规则 JSON 格式不规范,请改为规范格式")
|
||||||
putAll(map)
|
putAll(map)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ object DirectLinkUpload {
|
|||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
data class Rule(
|
data class Rule(
|
||||||
var uploadUrl: String, //上传url
|
var uploadUrl: String, //创建分享链接
|
||||||
var downloadUrlRule: String, //下载链接规则
|
var downloadUrlRule: String, //下载链接规则
|
||||||
var summary: String, //注释
|
var summary: String, //注释
|
||||||
var compress: Boolean = false, //是否压缩
|
var compress: Boolean = false, //是否压缩
|
||||||
|
|||||||
@@ -988,6 +988,7 @@ interface JsExtensions : JsEncodeUtils {
|
|||||||
|
|
||||||
// 新增 mimeType 参数,默认为 null(保持兼容性)
|
// 新增 mimeType 参数,默认为 null(保持兼容性)
|
||||||
fun openUrl(url: String, mimeType: String? = null) {
|
fun openUrl(url: String, mimeType: String? = null) {
|
||||||
|
require(url.length < 64 * 1024) { "openUrl parameter url too long" }
|
||||||
rhinoContext.ensureActive()
|
rhinoContext.ensureActive()
|
||||||
val source = getSource() ?: throw NoStackTraceException("openUrl source cannot be null")
|
val source = getSource() ?: throw NoStackTraceException("openUrl source cannot be null")
|
||||||
appCtx.startActivity<OpenUrlConfirmActivity> {
|
appCtx.startActivity<OpenUrlConfirmActivity> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.legato.kazusa.help.book
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.script.rhino.runScriptWithContext
|
||||||
import io.legato.kazusa.constant.AppLog
|
import io.legato.kazusa.constant.AppLog
|
||||||
import io.legato.kazusa.constant.AppPattern
|
import io.legato.kazusa.constant.AppPattern
|
||||||
import io.legato.kazusa.constant.EventBus
|
import io.legato.kazusa.constant.EventBus
|
||||||
@@ -28,7 +29,6 @@ import io.legato.kazusa.utils.getFile
|
|||||||
import io.legato.kazusa.utils.isContentScheme
|
import io.legato.kazusa.utils.isContentScheme
|
||||||
import io.legato.kazusa.utils.onEachParallel
|
import io.legato.kazusa.utils.onEachParallel
|
||||||
import io.legato.kazusa.utils.postEvent
|
import io.legato.kazusa.utils.postEvent
|
||||||
import com.script.rhino.runScriptWithContext
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
@@ -157,7 +157,7 @@ object BookHelp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveContent(
|
fun saveContent(
|
||||||
bookSource: BookSource,
|
bookSource: BookSource,
|
||||||
book: Book,
|
book: Book,
|
||||||
bookChapter: BookChapter,
|
bookChapter: BookChapter,
|
||||||
@@ -404,7 +404,11 @@ object BookHelp {
|
|||||||
bookChapter.getFileName()
|
bookChapter.getFileName()
|
||||||
)
|
)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
return file.readText()
|
val string = file.readText()
|
||||||
|
if (string.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return string
|
||||||
}
|
}
|
||||||
if (book.isLocal) {
|
if (book.isLocal) {
|
||||||
val string = LocalBook.getContent(book, bookChapter)
|
val string = LocalBook.getContent(book, bookChapter)
|
||||||
|
|||||||
@@ -570,6 +570,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
val mouseWheelPage
|
val mouseWheelPage
|
||||||
get() = appCtx.getPrefBoolean(PreferKey.mouseWheelPage, true)
|
get() = appCtx.getPrefBoolean(PreferKey.mouseWheelPage, true)
|
||||||
|
|
||||||
|
val paddingDisplayCutouts
|
||||||
|
get() = appCtx.getPrefBoolean(PreferKey.paddingDisplayCutouts, false)
|
||||||
|
|
||||||
var searchScope: String
|
var searchScope: String
|
||||||
get() = appCtx.getPrefString("searchScope") ?: ""
|
get() = appCtx.getPrefString("searchScope") ?: ""
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import android.graphics.drawable.BitmapDrawable
|
|||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import io.legato.kazusa.R
|
|
||||||
import io.legato.kazusa.constant.AppLog
|
import io.legato.kazusa.constant.AppLog
|
||||||
import io.legato.kazusa.constant.PageAnim
|
import io.legato.kazusa.constant.PageAnim
|
||||||
import io.legato.kazusa.constant.PreferKey
|
import io.legato.kazusa.constant.PreferKey
|
||||||
@@ -30,8 +30,6 @@ import io.legato.kazusa.utils.printOnDebug
|
|||||||
import io.legato.kazusa.utils.putPrefBoolean
|
import io.legato.kazusa.utils.putPrefBoolean
|
||||||
import io.legato.kazusa.utils.putPrefInt
|
import io.legato.kazusa.utils.putPrefInt
|
||||||
import io.legato.kazusa.utils.resizeAndRecycle
|
import io.legato.kazusa.utils.resizeAndRecycle
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -439,9 +437,7 @@ object ReadBookConfig {
|
|||||||
return exportConfig
|
return exportConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun import(byteArray: ByteArray): Result<Config> {
|
fun import(byteArray: ByteArray): Config {
|
||||||
return kotlin.runCatching {
|
|
||||||
withContext(IO) {
|
|
||||||
val configZipPath = FileUtils.getPath(appCtx.externalCache, "readConfig.zip")
|
val configZipPath = FileUtils.getPath(appCtx.externalCache, "readConfig.zip")
|
||||||
FileUtils.delete(configZipPath)
|
FileUtils.delete(configZipPath)
|
||||||
val zipFile = FileUtils.createFileIfNotExist(configZipPath)
|
val zipFile = FileUtils.createFileIfNotExist(configZipPath)
|
||||||
@@ -470,19 +466,9 @@ object ReadBookConfig {
|
|||||||
bgFile.copyTo(File(bgPath))
|
bgFile.copyTo(File(bgPath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.bgStr = bgPath
|
|
||||||
}
|
|
||||||
if (config.bgTypeNight == 2) {
|
|
||||||
val bgName = FileUtils.getName(config.bgStrNight)
|
|
||||||
config.bgStrNight = bgName
|
|
||||||
val bgPath = FileUtils.getPath(appCtx.externalFiles, "bg", bgName)
|
|
||||||
if (!FileUtils.exist(bgPath)) {
|
|
||||||
val bgFile = configDir.getFile(bgName)
|
|
||||||
if (bgFile.exists()) {
|
|
||||||
bgFile.copyTo(File(bgPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.bgStrNight = bgPath
|
config.bgStrNight = bgPath
|
||||||
|
} else if (config.bgTypeNight == 0) {
|
||||||
|
config.bgStrNight.toColorInt()
|
||||||
}
|
}
|
||||||
if (config.bgTypeEInk == 2) {
|
if (config.bgTypeEInk == 2) {
|
||||||
val bgName = FileUtils.getName(config.bgStrEInk)
|
val bgName = FileUtils.getName(config.bgStrEInk)
|
||||||
@@ -495,10 +481,11 @@ object ReadBookConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.bgStrEInk = bgPath
|
config.bgStrEInk = bgPath
|
||||||
|
} else if (config.bgTypeEInk == 0) {
|
||||||
|
config.bgStrEInk.toColorInt()
|
||||||
}
|
}
|
||||||
return@withContext config
|
config.curTextColor()
|
||||||
}
|
return config
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@@ -2,19 +2,18 @@ package io.legato.kazusa.help.config
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
import io.legato.kazusa.R
|
import io.legato.kazusa.R
|
||||||
|
import io.legato.kazusa.constant.AppLog
|
||||||
import io.legato.kazusa.constant.EventBus
|
import io.legato.kazusa.constant.EventBus
|
||||||
import io.legato.kazusa.constant.PreferKey
|
import io.legato.kazusa.constant.PreferKey
|
||||||
import io.legato.kazusa.constant.Theme
|
import io.legato.kazusa.constant.Theme
|
||||||
import io.legato.kazusa.help.DefaultData
|
import io.legato.kazusa.help.DefaultData
|
||||||
import io.legato.kazusa.lib.theme.ThemeStore
|
|
||||||
import io.legato.kazusa.model.BookCover
|
import io.legato.kazusa.model.BookCover
|
||||||
import io.legato.kazusa.utils.BitmapUtils
|
import io.legato.kazusa.utils.BitmapUtils
|
||||||
import io.legato.kazusa.utils.ColorUtils
|
|
||||||
import io.legato.kazusa.utils.FileUtils
|
import io.legato.kazusa.utils.FileUtils
|
||||||
import io.legato.kazusa.utils.GSON
|
import io.legato.kazusa.utils.GSON
|
||||||
import io.legato.kazusa.utils.fromJsonArray
|
import io.legato.kazusa.utils.fromJsonArray
|
||||||
@@ -29,7 +28,6 @@ import io.legato.kazusa.utils.putPrefInt
|
|||||||
import io.legato.kazusa.utils.stackBlur
|
import io.legato.kazusa.utils.stackBlur
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.core.graphics.toColorInt
|
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
object ThemeConfig {
|
object ThemeConfig {
|
||||||
@@ -117,13 +115,18 @@ object ThemeConfig {
|
|||||||
fun addConfig(json: String): Boolean {
|
fun addConfig(json: String): Boolean {
|
||||||
GSON.fromJsonObject<Config>(json.trim { it < ' ' }).getOrNull()
|
GSON.fromJsonObject<Config>(json.trim { it < ' ' }).getOrNull()
|
||||||
?.let {
|
?.let {
|
||||||
|
if (validateConfig(it)) {
|
||||||
addConfig(it)
|
addConfig(it)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addConfig(newConfig: Config) {
|
fun addConfig(newConfig: Config) {
|
||||||
|
if (!validateConfig(newConfig)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
configList.forEachIndexed { index, config ->
|
configList.forEachIndexed { index, config ->
|
||||||
if (newConfig.themeName == config.themeName) {
|
if (newConfig.themeName == config.themeName) {
|
||||||
configList[index] = newConfig
|
configList[index] = newConfig
|
||||||
@@ -134,6 +137,18 @@ object ThemeConfig {
|
|||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun validateConfig(config: Config): Boolean {
|
||||||
|
try {
|
||||||
|
config.primaryColor.toColorInt()
|
||||||
|
config.accentColor.toColorInt()
|
||||||
|
config.backgroundColor.toColorInt()
|
||||||
|
config.bottomBackground.toColorInt()
|
||||||
|
return true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getConfigs(): List<Config>? {
|
private fun getConfigs(): List<Config>? {
|
||||||
val configFile = File(configFilePath)
|
val configFile = File(configFilePath)
|
||||||
if (configFile.exists()) {
|
if (configFile.exists()) {
|
||||||
@@ -148,6 +163,7 @@ object ThemeConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun applyConfig(context: Context, config: Config) {
|
fun applyConfig(context: Context, config: Config) {
|
||||||
|
try {
|
||||||
val primary = config.primaryColor.toColorInt()
|
val primary = config.primaryColor.toColorInt()
|
||||||
val accent = config.accentColor.toColorInt()
|
val accent = config.accentColor.toColorInt()
|
||||||
val background = config.backgroundColor.toColorInt()
|
val background = config.backgroundColor.toColorInt()
|
||||||
@@ -165,6 +181,9 @@ object ThemeConfig {
|
|||||||
}
|
}
|
||||||
AppConfig.isNightTheme = config.isNightTheme
|
AppConfig.isNightTheme = config.isNightTheme
|
||||||
applyDayNight(context)
|
applyDayNight(context)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
AppLog.put("设置主题出错\n$e", e, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveDayTheme(context: Context, name: String) {
|
fun saveDayTheme(context: Context, name: String) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.security.MessageDigest
|
|||||||
* @radius: 0..25
|
* @radius: 0..25
|
||||||
*/
|
*/
|
||||||
class BlurTransformation(
|
class BlurTransformation(
|
||||||
@IntRange(from = 0, to = 25) private val radius: Int
|
@param:IntRange(from = 0, to = 25) private val radius: Int
|
||||||
) : BitmapTransformation() {
|
) : BitmapTransformation() {
|
||||||
|
|
||||||
override fun transform(
|
override fun transform(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.legato.kazusa.help.http
|
|||||||
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody
|
||||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||||
import okhttp3.internal.http.promisesBody
|
import okhttp3.internal.http.promisesBody
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
@@ -24,7 +25,7 @@ object DecompressInterceptor : Interceptor {
|
|||||||
val response = chain.proceed(requestBuilder.build())
|
val response = chain.proceed(requestBuilder.build())
|
||||||
val body = response.body
|
val body = response.body
|
||||||
|
|
||||||
if (!transparentDecompress || !response.promisesBody() || body == null) {
|
if (!transparentDecompress || !response.promisesBody() || body == ResponseBody.EMPTY) {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ val okHttpClientManga by lazy {
|
|||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
val url = request.url.toString()
|
val url = request.url.toString()
|
||||||
response.newBuilder()
|
response.newBuilder()
|
||||||
.body(ProgressResponseBody(url, LISTENER, response.body!!))
|
.body(ProgressResponseBody(url, LISTENER, response.body))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
interceptors.add(1) { chain ->
|
interceptors.add(1) { chain ->
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import okhttp3.Protocol
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.internal.notifyAll
|
|
||||||
import okhttp3.internal.wait
|
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import okio.BufferedSink
|
import okio.BufferedSink
|
||||||
import okio.Pipe
|
import okio.Pipe
|
||||||
@@ -186,7 +184,7 @@ class ObsoleteUrlFactory(private var client: OkHttpClient) : URLStreamHandlerFac
|
|||||||
return try {
|
return try {
|
||||||
val response = getResponse(true)
|
val response = getResponse(true)
|
||||||
if (hasBody(response) && response.code >= HTTP_BAD_REQUEST) {
|
if (hasBody(response) && response.code >= HTTP_BAD_REQUEST) {
|
||||||
response.body!!.byteStream()
|
response.body.byteStream()
|
||||||
} else null
|
} else null
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
null
|
null
|
||||||
@@ -258,7 +256,7 @@ class ObsoleteUrlFactory(private var client: OkHttpClient) : URLStreamHandlerFac
|
|||||||
}
|
}
|
||||||
val response = getResponse(false)
|
val response = getResponse(false)
|
||||||
if (response.code >= HTTP_BAD_REQUEST) throw FileNotFoundException(url.toString())
|
if (response.code >= HTTP_BAD_REQUEST) throw FileNotFoundException(url.toString())
|
||||||
return response.body!!.byteStream()
|
return response.body.byteStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@@ -1174,6 +1172,14 @@ class ObsoleteUrlFactory(private var client: OkHttpClient) : URLStreamHandlerFac
|
|||||||
throw AssertionError()
|
throw AssertionError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
|
||||||
|
private inline fun Any.wait() = (this as Object).wait()
|
||||||
|
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
|
||||||
|
private inline fun Any.notify() = (this as Object).notify()
|
||||||
|
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
|
||||||
|
private inline fun Any.notifyAll() = (this as Object).notifyAll()
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ object SourceVerificationHelp {
|
|||||||
* 获取书源验证结果
|
* 获取书源验证结果
|
||||||
* 图片验证码 防爬 滑动验证码 点击字符 等等
|
* 图片验证码 防爬 滑动验证码 点击字符 等等
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun getVerificationResult(
|
fun getVerificationResult(
|
||||||
source: BaseSource?,
|
source: BaseSource?,
|
||||||
url: String,
|
url: String,
|
||||||
@@ -38,9 +39,8 @@ object SourceVerificationHelp {
|
|||||||
): String {
|
): String {
|
||||||
source
|
source
|
||||||
?: throw NoStackTraceException("getVerificationResult parameter source cannot be null")
|
?: throw NoStackTraceException("getVerificationResult parameter source cannot be null")
|
||||||
if (isMainThread) {
|
require(url.length < 64 * 1024) { "getVerificationResult parameter url too long" }
|
||||||
error("getVerificationResult must be called on a background thread")
|
check(!isMainThread) { "getVerificationResult must be called on a background thread" }
|
||||||
}
|
|
||||||
|
|
||||||
clearResult(source.getKey())
|
clearResult(source.getKey())
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ object SourceVerificationHelp {
|
|||||||
refetchAfterSuccess: Boolean? = true
|
refetchAfterSuccess: Boolean? = true
|
||||||
) {
|
) {
|
||||||
source ?: throw NoStackTraceException("startBrowser parameter source cannot be null")
|
source ?: throw NoStackTraceException("startBrowser parameter source cannot be null")
|
||||||
|
require(url.length < 64 * 1024) { "startBrowser parameter url too long" }
|
||||||
appCtx.startActivity<WebViewActivity> {
|
appCtx.startActivity<WebViewActivity> {
|
||||||
putExtra("title", title)
|
putExtra("title", title)
|
||||||
putExtra("url", url)
|
putExtra("url", url)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legato.kazusa.help.storage
|
package io.legato.kazusa.help.storage
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
@@ -106,7 +107,6 @@ object Restore {
|
|||||||
.forEach { book ->
|
.forEach { book ->
|
||||||
book.coverUrl = LocalBook.getCoverPath(book)
|
book.coverUrl = LocalBook.getCoverPath(book)
|
||||||
}
|
}
|
||||||
val updateBooks = arrayListOf<Book>()
|
|
||||||
val newBooks = arrayListOf<Book>()
|
val newBooks = arrayListOf<Book>()
|
||||||
val ignoreLocalBook = BackupConfig.ignoreLocalBook
|
val ignoreLocalBook = BackupConfig.ignoreLocalBook
|
||||||
it.forEach { book ->
|
it.forEach { book ->
|
||||||
@@ -114,12 +114,15 @@ object Restore {
|
|||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
if (appDb.bookDao.has(book.bookUrl)) {
|
if (appDb.bookDao.has(book.bookUrl)) {
|
||||||
updateBooks.add(book)
|
try {
|
||||||
|
appDb.bookDao.update(book)
|
||||||
|
} catch (_: SQLiteConstraintException) {
|
||||||
|
appDb.bookDao.insert(book)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newBooks.add(book)
|
newBooks.add(book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appDb.bookDao.update(*updateBooks.toTypedArray())
|
|
||||||
appDb.bookDao.insert(*newBooks.toTypedArray())
|
appDb.bookDao.insert(*newBooks.toTypedArray())
|
||||||
}
|
}
|
||||||
fileToListT<Bookmark>(path, "bookmark.json")?.let {
|
fileToListT<Bookmark>(path, "bookmark.json")?.let {
|
||||||
|
|||||||
@@ -9,12 +9,19 @@ import io.legato.kazusa.utils.DebugLog
|
|||||||
import io.legato.kazusa.utils.asIOException
|
import io.legato.kazusa.utils.asIOException
|
||||||
import io.legato.kazusa.utils.splitNotBlank
|
import io.legato.kazusa.utils.splitNotBlank
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import okhttp3.*
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
import okhttp3.EventListener
|
import okhttp3.EventListener
|
||||||
|
import okhttp3.Headers
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.Protocol
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody
|
||||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||||
|
import okhttp3.internal.http.HTTP_PERM_REDIRECT
|
||||||
|
import okhttp3.internal.http.HTTP_TEMP_REDIRECT
|
||||||
import okhttp3.internal.http.HttpMethod
|
import okhttp3.internal.http.HttpMethod
|
||||||
import okhttp3.internal.http.StatusLine
|
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import okio.Source
|
import okio.Source
|
||||||
import okio.Timeout
|
import okio.Timeout
|
||||||
@@ -25,7 +32,7 @@ import org.chromium.net.UrlResponseInfo
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.ProtocolException
|
import java.net.ProtocolException
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -98,9 +105,15 @@ abstract class AbsCallBack(
|
|||||||
followCount += 1
|
followCount += 1
|
||||||
urlResponseInfoChain.add(info)
|
urlResponseInfoChain.add(info)
|
||||||
val client = okHttpClient
|
val client = okHttpClient
|
||||||
if (originalRequest.url.isHttps && newLocationUrl.startsWith("http://") && client.followSslRedirects) {
|
if (originalRequest.url.isHttps
|
||||||
|
&& newLocationUrl.startsWith("http://")
|
||||||
|
&& client.followSslRedirects
|
||||||
|
) {
|
||||||
followRedirect = true
|
followRedirect = true
|
||||||
} else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) {
|
} else if (!originalRequest.url.isHttps
|
||||||
|
&& newLocationUrl.startsWith("https://")
|
||||||
|
&& client.followSslRedirects
|
||||||
|
) {
|
||||||
followRedirect = true
|
followRedirect = true
|
||||||
} else if (okHttpClient.followRedirects) {
|
} else if (okHttpClient.followRedirects) {
|
||||||
followRedirect = true
|
followRedirect = true
|
||||||
@@ -315,7 +328,7 @@ abstract class AbsCallBack(
|
|||||||
contentLength,
|
contentLength,
|
||||||
bodySource
|
bodySource
|
||||||
)
|
)
|
||||||
}
|
} ?: ResponseBody.EMPTY
|
||||||
|
|
||||||
return Response.Builder()
|
return Response.Builder()
|
||||||
.request(request)
|
.request(request)
|
||||||
@@ -381,9 +394,12 @@ abstract class AbsCallBack(
|
|||||||
if (HttpMethod.permitsRequestBody(method)) {
|
if (HttpMethod.permitsRequestBody(method)) {
|
||||||
val responseCode = userResponse.code
|
val responseCode = userResponse.code
|
||||||
val maintainBody = HttpMethod.redirectsWithBody(method) ||
|
val maintainBody = HttpMethod.redirectsWithBody(method) ||
|
||||||
responseCode == StatusLine.HTTP_PERM_REDIRECT ||
|
responseCode == HTTP_PERM_REDIRECT ||
|
||||||
responseCode == StatusLine.HTTP_TEMP_REDIRECT
|
responseCode == HTTP_TEMP_REDIRECT
|
||||||
if (HttpMethod.redirectsToGet(method) && responseCode != StatusLine.HTTP_PERM_REDIRECT && responseCode != StatusLine.HTTP_TEMP_REDIRECT) {
|
if (HttpMethod.redirectsToGet(method)
|
||||||
|
&& responseCode != HTTP_PERM_REDIRECT
|
||||||
|
&& responseCode != HTTP_TEMP_REDIRECT
|
||||||
|
) {
|
||||||
requestBuilder.method("GET", null)
|
requestBuilder.method("GET", null)
|
||||||
} else {
|
} else {
|
||||||
val requestBody = if (maintainBody) userResponse.request.body else null
|
val requestBody = if (maintainBody) userResponse.request.body else null
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import io.legato.kazusa.lib.mobi.entities.Ptagx
|
|||||||
import io.legato.kazusa.lib.mobi.entities.TOC
|
import io.legato.kazusa.lib.mobi.entities.TOC
|
||||||
import io.legato.kazusa.lib.mobi.entities.TagxHeader
|
import io.legato.kazusa.lib.mobi.entities.TagxHeader
|
||||||
import io.legato.kazusa.lib.mobi.entities.TagxTag
|
import io.legato.kazusa.lib.mobi.entities.TagxTag
|
||||||
|
import io.legato.kazusa.lib.mobi.utils.and
|
||||||
import io.legato.kazusa.lib.mobi.utils.readString
|
import io.legato.kazusa.lib.mobi.utils.readString
|
||||||
import io.legato.kazusa.lib.mobi.utils.readUInt16Array
|
import io.legato.kazusa.lib.mobi.utils.readUInt16Array
|
||||||
import io.legato.kazusa.lib.mobi.utils.readUInt32
|
import io.legato.kazusa.lib.mobi.utils.readUInt32
|
||||||
import io.legato.kazusa.lib.mobi.utils.readUInt8
|
import io.legato.kazusa.lib.mobi.utils.readUInt8
|
||||||
import okhttp3.internal.and
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package io.legato.kazusa.lib.mobi.utils
|
||||||
|
|
||||||
|
internal infix fun Byte.and(mask: Int): Int = toInt() and mask
|
||||||
|
|
||||||
|
internal infix fun Short.and(mask: Int): Int = toInt() and mask
|
||||||
|
|
||||||
|
internal infix fun Int.and(mask: Long): Long = toLong() and mask
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package io.legato.kazusa.lib.mobi.utils
|
package io.legato.kazusa.lib.mobi.utils
|
||||||
|
|
||||||
import okhttp3.internal.and
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import io.legato.kazusa.utils.toRequestBody
|
|||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
@@ -92,6 +93,7 @@ open class WebDav(
|
|||||||
.replace("+", "%20")
|
.replace("+", "%20")
|
||||||
.replace("%3A", ":")
|
.replace("%3A", ":")
|
||||||
.replace("%2F", "/")
|
.replace("%2F", "/")
|
||||||
|
raw.toHttpUrl().toString()
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
private val webDavClient by lazy {
|
private val webDavClient by lazy {
|
||||||
@@ -112,7 +114,14 @@ open class WebDav(
|
|||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val host: String? get() = url.host
|
private val host: String?
|
||||||
|
get() = url.host?.let {
|
||||||
|
if (it.startsWith("[")) {
|
||||||
|
it.substring(1, it.lastIndex)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前url文件信息
|
* 获取当前url文件信息
|
||||||
@@ -165,7 +174,7 @@ open class WebDav(
|
|||||||
method("PROPFIND", requestBody)
|
method("PROPFIND", requestBody)
|
||||||
}.apply {
|
}.apply {
|
||||||
checkResult(this)
|
checkResult(this)
|
||||||
}.body?.text()
|
}.body.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,8 +406,8 @@ open class WebDav(
|
|||||||
url(url)
|
url(url)
|
||||||
}.apply {
|
}.apply {
|
||||||
checkResult(this)
|
checkResult(this)
|
||||||
}.body?.byteStream()
|
}.body.byteStream()
|
||||||
return byteStream ?: throw WebDavException("WebDav下载出错\nNull Exception")
|
return byteStream
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -425,7 +434,7 @@ open class WebDav(
|
|||||||
*/
|
*/
|
||||||
private fun checkResult(response: Response) {
|
private fun checkResult(response: Response) {
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
val body = response.body?.string()
|
val body = response.body.string()
|
||||||
if (response.code == 401) {
|
if (response.code == 401) {
|
||||||
val headers = response.headers("WWW-Authenticate")
|
val headers = response.headers("WWW-Authenticate")
|
||||||
val supportBasicAuth = headers.any {
|
val supportBasicAuth = headers.any {
|
||||||
@@ -436,7 +445,7 @@ open class WebDav(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.message.isNotBlank() || body.isNullOrBlank()) {
|
if (response.message.isNotBlank() || body.isBlank()) {
|
||||||
throw WebDavException("${url}\n${response.code}:${response.message}")
|
throw WebDavException("${url}\n${response.code}:${response.message}")
|
||||||
}
|
}
|
||||||
val document = Jsoup.parse(body)
|
val document = Jsoup.parse(body)
|
||||||
|
|||||||
@@ -31,6 +31,22 @@ import splitties.init.appCtx
|
|||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object AudioPlay : CoroutineScope by MainScope() {
|
object AudioPlay : CoroutineScope by MainScope() {
|
||||||
|
|
||||||
|
enum class PlayMode(val iconRes: Int) {
|
||||||
|
LIST_END_STOP(R.drawable.ic_play_mode_list_end_stop),
|
||||||
|
RANDOM(R.drawable.ic_play_mode_random),
|
||||||
|
LIST_LOOP(R.drawable.ic_play_mode_list_loop);
|
||||||
|
|
||||||
|
fun next(): PlayMode {
|
||||||
|
return when (this) {
|
||||||
|
LIST_END_STOP -> RANDOM
|
||||||
|
RANDOM -> LIST_LOOP
|
||||||
|
LIST_LOOP -> LIST_END_STOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var playMode = PlayMode.LIST_END_STOP
|
||||||
var status = Status.STOP
|
var status = Status.STOP
|
||||||
private var activityContext: Context? = null
|
private var activityContext: Context? = null
|
||||||
private var serviceContext: Context? = null
|
private var serviceContext: Context? = null
|
||||||
@@ -48,6 +64,11 @@ object AudioPlay : CoroutineScope by MainScope() {
|
|||||||
var bookSource: BookSource? = null
|
var bookSource: BookSource? = null
|
||||||
val loadingChapters = arrayListOf<Int>()
|
val loadingChapters = arrayListOf<Int>()
|
||||||
|
|
||||||
|
fun changePlayMode() {
|
||||||
|
playMode = playMode.next()
|
||||||
|
postEvent(EventBus.PLAY_MODE_CHANGED, playMode)
|
||||||
|
}
|
||||||
|
|
||||||
fun upData(book: Book) {
|
fun upData(book: Book) {
|
||||||
AudioPlay.book = book
|
AudioPlay.book = book
|
||||||
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
|
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
|
||||||
@@ -259,6 +280,8 @@ object AudioPlay : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
fun next() {
|
fun next() {
|
||||||
stopPlay()
|
stopPlay()
|
||||||
|
when (playMode) {
|
||||||
|
PlayMode.LIST_END_STOP -> {
|
||||||
if (durChapterIndex + 1 < simulatedChapterSize) {
|
if (durChapterIndex + 1 < simulatedChapterSize) {
|
||||||
durChapterIndex += 1
|
durChapterIndex += 1
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
@@ -268,6 +291,24 @@ object AudioPlay : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayMode.RANDOM -> {
|
||||||
|
durChapterIndex = (0 until simulatedChapterSize).random()
|
||||||
|
durChapterPos = 0
|
||||||
|
durPlayUrl = ""
|
||||||
|
saveRead()
|
||||||
|
loadPlayUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayMode.LIST_LOOP -> {
|
||||||
|
durChapterIndex = (durChapterIndex + 1) % simulatedChapterSize
|
||||||
|
durChapterPos = 0
|
||||||
|
durPlayUrl = ""
|
||||||
|
saveRead()
|
||||||
|
loadPlayUrl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setTimer(minute: Int) {
|
fun setTimer(minute: Int) {
|
||||||
if (AudioPlayService.isRun) {
|
if (AudioPlayService.isRun) {
|
||||||
val intent = Intent(context, AudioPlayService::class.java)
|
val intent = Intent(context, AudioPlayService::class.java)
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ object BookCover {
|
|||||||
/**
|
/**
|
||||||
* 加载漫画图片
|
* 加载漫画图片
|
||||||
*/
|
*/
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
fun loadManga(
|
fun loadManga(
|
||||||
context: Context,
|
context: Context,
|
||||||
path: String?,
|
path: String?,
|
||||||
@@ -155,9 +154,10 @@ object BookCover {
|
|||||||
.skipMemoryCache(true).let {
|
.skipMemoryCache(true).let {
|
||||||
if (transformation != null) {
|
if (transformation != null) {
|
||||||
it.transform(transformation)
|
it.transform(transformation)
|
||||||
}
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import io.legato.kazusa.data.entities.BookSource
|
|||||||
import io.legato.kazusa.exception.ConcurrentException
|
import io.legato.kazusa.exception.ConcurrentException
|
||||||
import io.legato.kazusa.help.book.BookHelp
|
import io.legato.kazusa.help.book.BookHelp
|
||||||
import io.legato.kazusa.help.book.isLocal
|
import io.legato.kazusa.help.book.isLocal
|
||||||
|
import io.legato.kazusa.help.coroutine.CompositeCoroutine
|
||||||
import io.legato.kazusa.help.coroutine.Coroutine
|
import io.legato.kazusa.help.coroutine.Coroutine
|
||||||
import io.legato.kazusa.model.webBook.WebBook
|
import io.legato.kazusa.model.webBook.WebBook
|
||||||
import io.legato.kazusa.service.CacheBookService
|
import io.legato.kazusa.service.CacheBookService
|
||||||
@@ -96,15 +97,11 @@ object CacheBook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
successDownloadSet.clear()
|
|
||||||
errorDownloadMap.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
fun close() {
|
||||||
cacheBookMap.forEach { it.value.stop() }
|
cacheBookMap.forEach { it.value.stop() }
|
||||||
cacheBookMap.clear()
|
cacheBookMap.clear()
|
||||||
clear()
|
successDownloadSet.clear()
|
||||||
|
errorDownloadMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadSummary: String
|
val downloadSummary: String
|
||||||
@@ -146,6 +143,7 @@ object CacheBook {
|
|||||||
|
|
||||||
private val waitDownloadSet = linkedSetOf<Int>()
|
private val waitDownloadSet = linkedSetOf<Int>()
|
||||||
private val onDownloadSet = linkedSetOf<Int>()
|
private val onDownloadSet = linkedSetOf<Int>()
|
||||||
|
private val tasks = CompositeCoroutine()
|
||||||
private var isStopped = false
|
private var isStopped = false
|
||||||
private var waitingRetry = false
|
private var waitingRetry = false
|
||||||
|
|
||||||
@@ -158,7 +156,7 @@ object CacheBook {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun isRun(): Boolean {
|
fun isRun(): Boolean {
|
||||||
return waitDownloadSet.size > 0 || onDownloadSet.size > 0
|
return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -169,6 +167,7 @@ object CacheBook {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun stop() {
|
fun stop() {
|
||||||
waitDownloadSet.clear()
|
waitDownloadSet.clear()
|
||||||
|
tasks.clear()
|
||||||
isStopped = true
|
isStopped = true
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
||||||
}
|
}
|
||||||
@@ -309,6 +308,8 @@ object CacheBook {
|
|||||||
onCancel(chapterIndex)
|
onCancel(chapterIndex)
|
||||||
}.onFinally {
|
}.onFinally {
|
||||||
onFinally()
|
onFinally()
|
||||||
|
}.apply {
|
||||||
|
tasks.add(this)
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,20 +63,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
var isLocalBook = true
|
var isLocalBook = true
|
||||||
var chapterChanged = false
|
var chapterChanged = false
|
||||||
var prevTextChapter: TextChapter? = null
|
var prevTextChapter: TextChapter? = null
|
||||||
set(value) {
|
|
||||||
field?.cancelLayout()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
var curTextChapter: TextChapter? = null
|
var curTextChapter: TextChapter? = null
|
||||||
set(value) {
|
|
||||||
field?.cancelLayout()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
var nextTextChapter: TextChapter? = null
|
var nextTextChapter: TextChapter? = null
|
||||||
set(value) {
|
|
||||||
field?.cancelLayout()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
var bookSource: BookSource? = null
|
var bookSource: BookSource? = null
|
||||||
var msg: String? = null
|
var msg: String? = null
|
||||||
private val loadingChapters = arrayListOf<Int>()
|
private val loadingChapters = arrayListOf<Int>()
|
||||||
@@ -222,6 +210,9 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearTextChapter() {
|
fun clearTextChapter() {
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
|
curTextChapter?.cancelLayout()
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
curTextChapter = null
|
curTextChapter = null
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -333,6 +324,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex < simulatedChapterSize - 1) {
|
if (durChapterIndex < simulatedChapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = curTextChapter
|
prevTextChapter = curTextChapter
|
||||||
curTextChapter = nextTextChapter
|
curTextChapter = nextTextChapter
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -363,6 +355,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex < simulatedChapterSize - 1) {
|
if (durChapterIndex < simulatedChapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = curTextChapter
|
prevTextChapter = curTextChapter
|
||||||
curTextChapter = nextTextChapter
|
curTextChapter = nextTextChapter
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -394,6 +387,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex > 0) {
|
if (durChapterIndex > 0) {
|
||||||
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
||||||
durChapterIndex--
|
durChapterIndex--
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
nextTextChapter = curTextChapter
|
nextTextChapter = curTextChapter
|
||||||
curTextChapter = prevTextChapter
|
curTextChapter = prevTextChapter
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
@@ -703,6 +697,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
)
|
)
|
||||||
when (val offset = chapter.index - durChapterIndex) {
|
when (val offset = chapter.index - durChapterIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
|
curTextChapter?.cancelLayout()
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
var available = false
|
var available = false
|
||||||
@@ -727,12 +722,14 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-1 -> {
|
-1 -> {
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
textChapter.layoutChannel.receiveAsFlow().collect()
|
textChapter.layoutChannel.receiveAsFlow().collect()
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
if (page.index > 1) {
|
if (page.index > 1) {
|
||||||
@@ -776,6 +773,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
)
|
)
|
||||||
when (val offset = chapter.index - durChapterIndex) {
|
when (val offset = chapter.index - durChapterIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
|
curTextChapter?.cancelLayout()
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
var available = false
|
var available = false
|
||||||
@@ -800,12 +798,14 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-1 -> {
|
-1 -> {
|
||||||
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
textChapter.layoutChannel.receiveAsFlow().collect()
|
textChapter.layoutChannel.receiveAsFlow().collect()
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
|
nextTextChapter?.cancelLayout()
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
if (page.index > 1) {
|
if (page.index > 1) {
|
||||||
@@ -858,6 +858,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
fun saveRead(pageChanged: Boolean = false) {
|
fun saveRead(pageChanged: Boolean = false) {
|
||||||
executor.execute {
|
executor.execute {
|
||||||
|
kotlin.runCatching {
|
||||||
val book = book ?: return@execute
|
val book = book ?: return@execute
|
||||||
book.lastCheckCount = 0
|
book.lastCheckCount = 0
|
||||||
book.durChapterTime = System.currentTimeMillis()
|
book.durChapterTime = System.currentTimeMillis()
|
||||||
@@ -873,6 +874,9 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
|
}.onFailure {
|
||||||
|
AppLog.put("保存书籍阅读进度信息出错\n$it", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ object ReadManga : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
fun saveRead(pageChanged: Boolean = false) {
|
fun saveRead(pageChanged: Boolean = false) {
|
||||||
executor.execute {
|
executor.execute {
|
||||||
|
kotlin.runCatching {
|
||||||
val book = book ?: return@execute
|
val book = book ?: return@execute
|
||||||
book.lastCheckCount = 0
|
book.lastCheckCount = 0
|
||||||
book.durChapterTime = System.currentTimeMillis()
|
book.durChapterTime = System.currentTimeMillis()
|
||||||
@@ -342,6 +343,9 @@ object ReadManga : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
|
}.onFailure {
|
||||||
|
AppLog.put("保存漫画阅读进度信息出错\n$it", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import io.legato.kazusa.help.http.postForm
|
|||||||
import io.legato.kazusa.help.http.postJson
|
import io.legato.kazusa.help.http.postJson
|
||||||
import io.legato.kazusa.help.http.postMultipart
|
import io.legato.kazusa.help.http.postMultipart
|
||||||
import io.legato.kazusa.help.source.getShareScope
|
import io.legato.kazusa.help.source.getShareScope
|
||||||
import io.legato.kazusa.model.Debug
|
|
||||||
import io.legato.kazusa.utils.EncoderUtils
|
import io.legato.kazusa.utils.EncoderUtils
|
||||||
import io.legato.kazusa.utils.GSON
|
import io.legato.kazusa.utils.GSON
|
||||||
import io.legato.kazusa.utils.GSONStrict
|
import io.legato.kazusa.utils.GSONStrict
|
||||||
@@ -225,7 +224,7 @@ class AnalyzeUrl(
|
|||||||
if (urlOption == null) {
|
if (urlOption == null) {
|
||||||
urlOption = GSON.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
|
urlOption = GSON.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()
|
||||||
if (urlOption != null) {
|
if (urlOption != null) {
|
||||||
Debug.log("≡链接参数 JSON 格式不规范,请改为规范格式")
|
log("链接参数 JSON 格式不规范,请改为规范格式")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
urlOption?.let { option ->
|
urlOption?.let { option ->
|
||||||
@@ -247,11 +246,11 @@ class AnalyzeUrl(
|
|||||||
evalJS(jsStr, url)?.toString()?.let {
|
evalJS(jsStr, url)?.toString()?.let {
|
||||||
url = it
|
url = it
|
||||||
}
|
}
|
||||||
}
|
|
||||||
serverID = option.getServerID()
|
serverID = option.getServerID()
|
||||||
webViewDelayTime = max(0, option.getWebViewDelayTime() ?: 0)
|
webViewDelayTime = max(0, option.getWebViewDelayTime() ?: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
urlNoQuery = url
|
urlNoQuery = url
|
||||||
when (method) {
|
when (method) {
|
||||||
RequestMethod.GET -> {
|
RequestMethod.GET -> {
|
||||||
@@ -458,7 +457,7 @@ class AnalyzeUrl(
|
|||||||
else -> get(urlNoQuery, encodedQuery)
|
else -> get(urlNoQuery, encodedQuery)
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
val isXml = it.raw.body?.contentType()?.toString()
|
val isXml = it.raw.body.contentType()?.toString()
|
||||||
?.matches(AppPattern.xmlContentTypeRegex) == true
|
?.matches(AppPattern.xmlContentTypeRegex) == true
|
||||||
if (isXml && it.body?.trim()?.startsWith("<?xml", true) == false) {
|
if (isXml && it.body?.trim()?.startsWith("<?xml", true) == false) {
|
||||||
StrResponse(it.raw, "<?xml version=\"1.0\"?>" + it.body)
|
StrResponse(it.raw, "<?xml version=\"1.0\"?>" + it.body)
|
||||||
@@ -553,7 +552,7 @@ class AnalyzeUrl(
|
|||||||
getByteArrayIfDataUri()?.let {
|
getByteArrayIfDataUri()?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
return getResponseAwait().body!!.bytes()
|
return getResponseAwait().body.bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getByteArray(): ByteArray {
|
fun getByteArray(): ByteArray {
|
||||||
@@ -569,7 +568,7 @@ class AnalyzeUrl(
|
|||||||
getByteArrayIfDataUri()?.let {
|
getByteArrayIfDataUri()?.let {
|
||||||
return ByteArrayInputStream(it)
|
return ByteArrayInputStream(it)
|
||||||
}
|
}
|
||||||
return getResponseAwait().body!!.byteStream()
|
return getResponseAwait().body.byteStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInputStream(): InputStream {
|
fun getInputStream(): InputStream {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.legato.kazusa.model.localBook
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.script.ScriptBindings
|
import com.script.ScriptBindings
|
||||||
import com.script.rhino.RhinoScriptEngine
|
import com.script.rhino.RhinoScriptEngine
|
||||||
@@ -103,7 +104,7 @@ object LocalBook {
|
|||||||
|
|
||||||
fun getLastModified(book: Book): Result<Long> {
|
fun getLastModified(book: Book): Result<Long> {
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
val uri = Uri.parse(book.bookUrl)
|
val uri = book.bookUrl.toUri()
|
||||||
if (uri.isContentScheme()) {
|
if (uri.isContentScheme()) {
|
||||||
return@runCatching DocumentFile.fromSingleUri(appCtx, uri)!!.lastModified()
|
return@runCatching DocumentFile.fromSingleUri(appCtx, uri)!!.lastModified()
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,9 @@ object LocalBook {
|
|||||||
val list = ArrayList(LinkedHashSet(chapters))
|
val list = ArrayList(LinkedHashSet(chapters))
|
||||||
list.forEachIndexed { index, bookChapter ->
|
list.forEachIndexed { index, bookChapter ->
|
||||||
bookChapter.index = index
|
bookChapter.index = index
|
||||||
|
if (bookChapter.title.isEmpty()) {
|
||||||
|
bookChapter.title = "无标题章节"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val replaceRules = ContentProcessor.get(book).getTitleReplaceRules()
|
val replaceRules = ContentProcessor.get(book).getTitleReplaceRules()
|
||||||
book.durChapterTitle = list.getOrElse(book.durChapterIndex) { list.last() }
|
book.durChapterTitle = list.getOrElse(book.durChapterIndex) { list.last() }
|
||||||
@@ -191,6 +195,9 @@ object LocalBook {
|
|||||||
return StringEscapeUtils.unescapeHtml4(content)
|
return StringEscapeUtils.unescapeHtml4(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (content.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +381,7 @@ object LocalBook {
|
|||||||
}
|
}
|
||||||
if (deleteOriginal) {
|
if (deleteOriginal) {
|
||||||
if (book.bookUrl.isContentScheme()) {
|
if (book.bookUrl.isContentScheme()) {
|
||||||
val uri = Uri.parse(book.bookUrl)
|
val uri = book.bookUrl.toUri()
|
||||||
DocumentFile.fromSingleUri(appCtx, uri)?.delete()
|
DocumentFile.fromSingleUri(appCtx, uri)?.delete()
|
||||||
} else {
|
} else {
|
||||||
FileUtils.delete(book.bookUrl)
|
FileUtils.delete(book.bookUrl)
|
||||||
@@ -419,7 +426,7 @@ object LocalBook {
|
|||||||
inputStream.use {
|
inputStream.use {
|
||||||
val defaultBookTreeUri = AppConfig.defaultBookTreeUri
|
val defaultBookTreeUri = AppConfig.defaultBookTreeUri
|
||||||
if (defaultBookTreeUri.isNullOrBlank()) throw NoBooksDirException()
|
if (defaultBookTreeUri.isNullOrBlank()) throw NoBooksDirException()
|
||||||
val treeUri = Uri.parse(defaultBookTreeUri)
|
val treeUri = defaultBookTreeUri.toUri()
|
||||||
return if (treeUri.isContentScheme()) {
|
return if (treeUri.isContentScheme()) {
|
||||||
val treeDoc = DocumentFile.fromTreeUri(appCtx, treeUri)
|
val treeDoc = DocumentFile.fromTreeUri(appCtx, treeUri)
|
||||||
var doc = treeDoc!!.findFile(fileName)
|
var doc = treeDoc!!.findFile(fileName)
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ class MobiFile(var book: Book) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
section = section.next ?: break
|
section = section.next ?: break
|
||||||
if (section.linear) {
|
if (!section.linear) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (section.href == nextSectionHref) {
|
if (section.href == nextSectionHref) {
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ class TextFile(private var book: Book) {
|
|||||||
val curChapter = BookChapter()
|
val curChapter = BookChapter()
|
||||||
curChapter.title = matcher.group()
|
curChapter.title = matcher.group()
|
||||||
curChapter.start = curOffset + chapterLength
|
curChapter.start = curOffset + chapterLength
|
||||||
|
curChapter.end = curChapter.start
|
||||||
toc.add(curChapter)
|
toc.add(curChapter)
|
||||||
lastChapterWordCount = 0
|
lastChapterWordCount = 0
|
||||||
} else if (seekPos == 0 && chapterStart != 0) {
|
} else if (seekPos == 0 && chapterStart != 0) {
|
||||||
@@ -242,6 +243,7 @@ class TextFile(private var book: Book) {
|
|||||||
val curChapter = BookChapter()
|
val curChapter = BookChapter()
|
||||||
curChapter.title = matcher.group()
|
curChapter.title = matcher.group()
|
||||||
curChapter.start = curOffset + chapterLength
|
curChapter.start = curOffset + chapterLength
|
||||||
|
curChapter.end = curChapter.start
|
||||||
toc.add(curChapter)
|
toc.add(curChapter)
|
||||||
} else { //否则就block分割之后,上一个章节的剩余内容
|
} else { //否则就block分割之后,上一个章节的剩余内容
|
||||||
//获取上一章节
|
//获取上一章节
|
||||||
@@ -257,6 +259,7 @@ class TextFile(private var book: Book) {
|
|||||||
val curChapter = BookChapter()
|
val curChapter = BookChapter()
|
||||||
curChapter.title = matcher.group()
|
curChapter.title = matcher.group()
|
||||||
curChapter.start = lastChapter.end
|
curChapter.start = lastChapter.end
|
||||||
|
curChapter.end = curChapter.start
|
||||||
toc.add(curChapter)
|
toc.add(curChapter)
|
||||||
}
|
}
|
||||||
bookWordCount += chapterContent.length
|
bookWordCount += chapterContent.length
|
||||||
@@ -275,6 +278,7 @@ class TextFile(private var book: Book) {
|
|||||||
val curChapter = BookChapter()
|
val curChapter = BookChapter()
|
||||||
curChapter.title = matcher.group()
|
curChapter.title = matcher.group()
|
||||||
curChapter.start = lastChapter.end
|
curChapter.start = lastChapter.end
|
||||||
|
curChapter.end = curChapter.start
|
||||||
toc.add(curChapter)
|
toc.add(curChapter)
|
||||||
} else { //如果章节不存在则创建章节
|
} else { //如果章节不存在则创建章节
|
||||||
val curChapter = BookChapter()
|
val curChapter = BookChapter()
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -39,7 +41,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
private var bookSourceParts = emptyList<BookSourcePart>()
|
private var bookSourceParts = emptyList<BookSourcePart>()
|
||||||
private var searchBooks = arrayListOf<SearchBook>()
|
private var searchBooks = arrayListOf<SearchBook>()
|
||||||
private var searchJob: Job? = null
|
private var searchJob: Job? = null
|
||||||
|
private var workingState = MutableStateFlow(true)
|
||||||
|
|
||||||
private fun initSearchPool() {
|
private fun initSearchPool() {
|
||||||
searchPool?.close()
|
searchPool?.close()
|
||||||
@@ -82,6 +84,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
bs.getBookSource()?.let {
|
bs.getBookSource()?.let {
|
||||||
emit(it)
|
emit(it)
|
||||||
}
|
}
|
||||||
|
workingState.first { it }
|
||||||
}
|
}
|
||||||
}.onStart {
|
}.onStart {
|
||||||
callBack.onSearchStart()
|
callBack.onSearchStart()
|
||||||
@@ -191,6 +194,14 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
mSearchId = 0L
|
mSearchId = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
workingState.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
workingState.value = true
|
||||||
|
}
|
||||||
|
|
||||||
interface CallBack {
|
interface CallBack {
|
||||||
fun getSearchScope(): SearchScope
|
fun getSearchScope(): SearchScope
|
||||||
fun onSearchStart()
|
fun onSearchStart()
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import io.legato.kazusa.R
|
|||||||
import io.legato.kazusa.base.BaseService
|
import io.legato.kazusa.base.BaseService
|
||||||
import io.legato.kazusa.constant.AppConst
|
import io.legato.kazusa.constant.AppConst
|
||||||
import io.legato.kazusa.constant.AppLog
|
import io.legato.kazusa.constant.AppLog
|
||||||
|
import io.legato.kazusa.constant.AppPattern
|
||||||
import io.legato.kazusa.constant.EventBus
|
import io.legato.kazusa.constant.EventBus
|
||||||
import io.legato.kazusa.constant.IntentAction
|
import io.legato.kazusa.constant.IntentAction
|
||||||
import io.legato.kazusa.constant.NotificationId
|
import io.legato.kazusa.constant.NotificationId
|
||||||
@@ -45,9 +46,7 @@ import io.legato.kazusa.ui.book.read.ReadBookActivity
|
|||||||
import io.legato.kazusa.ui.book.read.page.entities.TextChapter
|
import io.legato.kazusa.ui.book.read.page.entities.TextChapter
|
||||||
import io.legato.kazusa.utils.LogUtils
|
import io.legato.kazusa.utils.LogUtils
|
||||||
import io.legato.kazusa.utils.activityPendingIntent
|
import io.legato.kazusa.utils.activityPendingIntent
|
||||||
import io.legato.kazusa.utils.broadcastPendingIntent
|
|
||||||
import io.legato.kazusa.utils.getPrefBoolean
|
import io.legato.kazusa.utils.getPrefBoolean
|
||||||
import io.legato.kazusa.utils.isVivoDevice
|
|
||||||
import io.legato.kazusa.utils.observeEvent
|
import io.legato.kazusa.utils.observeEvent
|
||||||
import io.legato.kazusa.utils.observeSharedPreferences
|
import io.legato.kazusa.utils.observeSharedPreferences
|
||||||
import io.legato.kazusa.utils.postEvent
|
import io.legato.kazusa.utils.postEvent
|
||||||
@@ -326,9 +325,11 @@ abstract class BaseReadAloudService : BaseService(),
|
|||||||
private fun prevP() {
|
private fun prevP() {
|
||||||
if (nowSpeak > 0) {
|
if (nowSpeak > 0) {
|
||||||
playStop()
|
playStop()
|
||||||
|
do {
|
||||||
nowSpeak--
|
nowSpeak--
|
||||||
readAloudNumber -= contentList[nowSpeak].length + 1 + paragraphStartPos
|
readAloudNumber -= contentList[nowSpeak].length + 1 + paragraphStartPos
|
||||||
paragraphStartPos = 0
|
paragraphStartPos = 0
|
||||||
|
} while (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex))
|
||||||
textChapter?.let {
|
textChapter?.let {
|
||||||
if (readAloudByPage) {
|
if (readAloudByPage) {
|
||||||
val paragraphs = it.getParagraphs(true)
|
val paragraphs = it.getParagraphs(true)
|
||||||
@@ -358,7 +359,9 @@ abstract class BaseReadAloudService : BaseService(),
|
|||||||
val paragraphs = it.getParagraphs(true)
|
val paragraphs = it.getParagraphs(true)
|
||||||
if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber--
|
if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber--
|
||||||
}
|
}
|
||||||
if (readAloudNumber >= it.getReadLength(pageIndex + 1)) {
|
if (pageIndex + 1 < it.pageSize
|
||||||
|
&& readAloudNumber >= it.getReadLength(pageIndex + 1)
|
||||||
|
) {
|
||||||
pageIndex++
|
pageIndex++
|
||||||
ReadBook.moveToNextPage()
|
ReadBook.moveToNextPage()
|
||||||
}
|
}
|
||||||
@@ -443,24 +446,75 @@ abstract class BaseReadAloudService : BaseService(),
|
|||||||
PlaybackStateCompat.Builder()
|
PlaybackStateCompat.Builder()
|
||||||
.setActions(MediaHelp.MEDIA_SESSION_ACTIONS)
|
.setActions(MediaHelp.MEDIA_SESSION_ACTIONS)
|
||||||
.setState(state, nowSpeak.toLong(), 1f)
|
.setState(state, nowSpeak.toLong(), 1f)
|
||||||
|
// 为系统媒体控件添加定时按钮
|
||||||
|
.addCustomAction(
|
||||||
|
PlaybackStateCompat.CustomAction.Builder(
|
||||||
|
"ACTION_ADD_TIMER",
|
||||||
|
getString(R.string.set_timer),
|
||||||
|
R.drawable.ic_time_add_24dp
|
||||||
|
).build()
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化MediaSession, 注册多媒体按钮
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* 初始化MediaSession, 注册多媒体按钮
|
* 初始化MediaSession, 注册多媒体按钮
|
||||||
*/
|
*/
|
||||||
@SuppressLint("UnspecifiedImmutableFlag")
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
private fun initMediaSession() {
|
private fun initMediaSession() {
|
||||||
|
if (getPrefBoolean("systemMediaControlCompatibilityChange")) {
|
||||||
mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
|
mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
|
||||||
|
override fun onPlay() {
|
||||||
|
resumeReadAloud()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
pauseReadAloud()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSkipToNext() {
|
||||||
|
if (getPrefBoolean("mediaButtonPerNext", false)) {
|
||||||
|
nextChapter()
|
||||||
|
} else {
|
||||||
|
nextP()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSkipToPrevious() {
|
||||||
|
if (getPrefBoolean("mediaButtonPerNext", false)) {
|
||||||
|
prevChapter()
|
||||||
|
} else {
|
||||||
|
prevP()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCustomAction(action: String, extras: Bundle?) {
|
||||||
|
if (action == "ACTION_ADD_TIMER") addTimer()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
||||||
return MediaButtonReceiver.handleIntent(this@BaseReadAloudService, mediaButtonEvent)
|
return MediaButtonReceiver.handleIntent(
|
||||||
|
this@BaseReadAloudService, mediaButtonEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mediaSessionCompat.setMediaButtonReceiver(
|
} else {
|
||||||
broadcastPendingIntent<MediaButtonReceiver>(Intent.ACTION_MEDIA_BUTTON)
|
mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
|
||||||
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
||||||
|
return MediaButtonReceiver.handleIntent(
|
||||||
|
this@BaseReadAloudService, mediaButtonEvent
|
||||||
)
|
)
|
||||||
mediaSessionCompat.isActive = true
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -523,7 +577,7 @@ abstract class BaseReadAloudService : BaseService(),
|
|||||||
private fun choiceMediaStyle(): androidx.media.app.NotificationCompat.MediaStyle {
|
private fun choiceMediaStyle(): androidx.media.app.NotificationCompat.MediaStyle {
|
||||||
val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()
|
val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()
|
||||||
.setShowActionsInCompactView(1, 2, 4)
|
.setShowActionsInCompactView(1, 2, 4)
|
||||||
if (isVivoDevice) {
|
if (getPrefBoolean("systemMediaControlCompatibilityChange")) {
|
||||||
//fix #4090 android 14 can not show play control in lock screen
|
//fix #4090 android 14 can not show play control in lock screen
|
||||||
mediaStyle.setMediaSession(mediaSessionCompat.sessionToken)
|
mediaStyle.setMediaSession(mediaSessionCompat.sessionToken)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ class CacheBookService : BaseService() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
isRun = true
|
isRun = true
|
||||||
CacheBook.clear()
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.legato.kazusa.service
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
@@ -306,7 +307,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createDownloader(factory: CacheDataSource.Factory, fileName: String): Downloader {
|
private fun createDownloader(factory: CacheDataSource.Factory, fileName: String): Downloader {
|
||||||
val uri = Uri.parse(fileName)
|
val uri = fileName.toUri()
|
||||||
val request = DownloadRequest.Builder(fileName, uri).build()
|
val request = DownloadRequest.Builder(fileName, uri).build()
|
||||||
return DefaultDownloaderFactory(factory, okHttpClient.dispatcher.executorService)
|
return DefaultDownloaderFactory(factory, okHttpClient.dispatcher.executorService)
|
||||||
.createDownloader(request)
|
.createDownloader(request)
|
||||||
@@ -343,17 +344,17 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
val contentType = contentType.substringBefore(";")
|
val contentType = contentType.substringBefore(";")
|
||||||
val ct = httpTts.contentType
|
val ct = httpTts.contentType
|
||||||
if (contentType == "application/json" || contentType.startsWith("text/")) {
|
if (contentType == "application/json" || contentType.startsWith("text/")) {
|
||||||
throw NoStackTraceException(response.body!!.string())
|
throw NoStackTraceException(response.body.string())
|
||||||
} else if (ct?.isNotBlank() == true) {
|
} else if (ct?.isNotBlank() == true) {
|
||||||
if (!contentType.matches(ct.toRegex())) {
|
if (!contentType.matches(ct.toRegex())) {
|
||||||
throw NoStackTraceException(
|
throw NoStackTraceException(
|
||||||
"TTS服务器返回错误:" + response.body!!.string()
|
"TTS服务器返回错误:" + response.body.string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
response.body!!.byteStream().let { stream ->
|
response.body.byteStream().let { stream ->
|
||||||
downloadErrorNo = 0
|
downloadErrorNo = 0
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
@@ -477,13 +478,13 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
val sleep = exoPlayer.duration / speakTextLength
|
val sleep = exoPlayer.duration / speakTextLength
|
||||||
val start = speakTextLength * exoPlayer.currentPosition / exoPlayer.duration
|
val start = speakTextLength * exoPlayer.currentPosition / exoPlayer.duration
|
||||||
for (i in start..contentList[nowSpeak].length) {
|
for (i in start..contentList[nowSpeak].length) {
|
||||||
if (readAloudNumber + i > textChapter.getReadLength(pageIndex + 1)) {
|
if (pageIndex + 1 < textChapter.pageSize
|
||||||
|
&& readAloudNumber + i > textChapter.getReadLength(pageIndex + 1)
|
||||||
|
) {
|
||||||
pageIndex++
|
pageIndex++
|
||||||
if (pageIndex < textChapter.pageSize) {
|
|
||||||
ReadBook.moveToNextPage()
|
ReadBook.moveToNextPage()
|
||||||
upTtsProgress(readAloudNumber + i.toInt())
|
upTtsProgress(readAloudNumber + i.toInt())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
delay(sleep)
|
delay(sleep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,9 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
|
|||||||
if (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex)) {
|
if (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex)) {
|
||||||
nextParagraph()
|
nextParagraph()
|
||||||
}
|
}
|
||||||
if (readAloudNumber + 1 > it.getReadLength(pageIndex + 1)) {
|
if (pageIndex + 1 < it.pageSize
|
||||||
|
&& readAloudNumber + 1 > it.getReadLength(pageIndex + 1)
|
||||||
|
) {
|
||||||
pageIndex++
|
pageIndex++
|
||||||
ReadBook.moveToNextPage()
|
ReadBook.moveToNextPage()
|
||||||
}
|
}
|
||||||
@@ -214,7 +216,9 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
|
|||||||
"onRangeStart nowSpeak:$nowSpeak pageIndex:$pageIndex utteranceId:$utteranceId start:$start end:$end frame:$frame"
|
"onRangeStart nowSpeak:$nowSpeak pageIndex:$pageIndex utteranceId:$utteranceId start:$start end:$end frame:$frame"
|
||||||
LogUtils.d(TAG, msg)
|
LogUtils.d(TAG, msg)
|
||||||
textChapter?.let {
|
textChapter?.let {
|
||||||
if (readAloudNumber + start > it.getReadLength(pageIndex + 1)) {
|
if (pageIndex + 1 < it.pageSize
|
||||||
|
&& readAloudNumber + start > it.getReadLength(pageIndex + 1)
|
||||||
|
) {
|
||||||
pageIndex++
|
pageIndex++
|
||||||
ReadBook.moveToNextPage()
|
ReadBook.moveToNextPage()
|
||||||
upTtsProgress(readAloudNumber + start)
|
upTtsProgress(readAloudNumber + start)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) {
|
|||||||
|
|
||||||
fun importReadConfig(bytes: ByteArray, finally: (title: String, msg: String) -> Unit) {
|
fun importReadConfig(bytes: ByteArray, finally: (title: String, msg: String) -> Unit) {
|
||||||
execute {
|
execute {
|
||||||
val config = ReadBookConfig.import(bytes).getOrThrow()
|
val config = ReadBookConfig.import(bytes)
|
||||||
ReadBookConfig.configList.forEachIndexed { index, c ->
|
ReadBookConfig.configList.forEachIndexed { index, c ->
|
||||||
if (c.name == config.name) {
|
if (c.name == config.name) {
|
||||||
ReadBookConfig.configList[index] = config
|
ReadBookConfig.configList[index] = config
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class AudioPlayActivity :
|
|||||||
override val viewModel by viewModels<AudioPlayViewModel>()
|
override val viewModel by viewModels<AudioPlayViewModel>()
|
||||||
private val timerSliderPopup by lazy { TimerSliderPopup(this) }
|
private val timerSliderPopup by lazy { TimerSliderPopup(this) }
|
||||||
private var adjustProgress = false
|
private var adjustProgress = false
|
||||||
|
private var playMode = AudioPlay.PlayMode.LIST_END_STOP
|
||||||
|
|
||||||
private val progressTimeFormat by lazy {
|
private val progressTimeFormat by lazy {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
@@ -140,6 +141,14 @@ class AudioPlayActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
|
binding.ivPlayMode.setOnClickListener {
|
||||||
|
AudioPlay.changePlayMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
observeEventSticky<AudioPlay.PlayMode>(EventBus.PLAY_MODE_CHANGED) {
|
||||||
|
playMode = it
|
||||||
|
updatePlayModeIcon()
|
||||||
|
}
|
||||||
binding.fabPlayStop.setOnClickListener {
|
binding.fabPlayStop.setOnClickListener {
|
||||||
playButton()
|
playButton()
|
||||||
}
|
}
|
||||||
@@ -207,6 +216,10 @@ class AudioPlayActivity :
|
|||||||
binding.llPlayMenu.applyNavigationBarPadding()
|
binding.llPlayMenu.applyNavigationBarPadding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updatePlayModeIcon() {
|
||||||
|
binding.ivPlayMode.setImageResource(playMode.iconRes)
|
||||||
|
}
|
||||||
|
|
||||||
private fun upCover(path: String?) {
|
private fun upCover(path: String?) {
|
||||||
BookCover.load(this, path, sourceOrigin = AudioPlay.bookSource?.bookSourceUrl) {
|
BookCover.load(this, path, sourceOrigin = AudioPlay.bookSource?.bookSourceUrl) {
|
||||||
BookCover.loadBlur(this, path, sourceOrigin = AudioPlay.bookSource?.bookSourceUrl)
|
BookCover.loadBlur(this, path, sourceOrigin = AudioPlay.bookSource?.bookSourceUrl)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
|
|||||||
|
|
||||||
fun initData(intent: Intent) = AudioPlay.apply {
|
fun initData(intent: Intent) = AudioPlay.apply {
|
||||||
execute {
|
execute {
|
||||||
val bookUrl = intent.getStringExtra("bookUrl") ?: return@execute
|
val bookUrl = intent.getStringExtra("bookUrl") ?: book?.bookUrl ?: return@execute
|
||||||
val book = appDb.bookDao.getBook(bookUrl) ?: return@execute
|
val book = appDb.bookDao.getBook(bookUrl) ?: return@execute
|
||||||
inBookshelf = intent.getBooleanExtra("inBookshelf", true)
|
inBookshelf = intent.getBooleanExtra("inBookshelf", true)
|
||||||
initBook(book)
|
initBook(book)
|
||||||
@@ -71,7 +71,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
|
|||||||
if (oldBook.bookUrl == book.bookUrl) {
|
if (oldBook.bookUrl == book.bookUrl) {
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
} else {
|
} else {
|
||||||
appDb.bookDao.insert(book)
|
appDb.bookDao.replace(oldBook, book)
|
||||||
}
|
}
|
||||||
appDb.bookChapterDao.delByBook(book.bookUrl)
|
appDb.bookChapterDao.delByBook(book.bookUrl)
|
||||||
appDb.bookChapterDao.insert(*cList.toTypedArray())
|
appDb.bookChapterDao.insert(*cList.toTypedArray())
|
||||||
|
|||||||
@@ -739,7 +739,7 @@ class BookInfoActivity :
|
|||||||
else -> readBookResult.launch(
|
else -> readBookResult.launch(
|
||||||
Intent(
|
Intent(
|
||||||
this,
|
this,
|
||||||
if (book.isImage && AppConfig.showMangaUi) ReadMangaActivity::class.java
|
if (!book.isLocal && book.isImage && AppConfig.showMangaUi) ReadMangaActivity::class.java
|
||||||
else ReadBookActivity::class.java
|
else ReadBookActivity::class.java
|
||||||
)
|
)
|
||||||
.putExtra("bookUrl", book.bookUrl)
|
.putExtra("bookUrl", book.bookUrl)
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
WebBook.getChapterList(scope, bookSource, book, runPreUpdateJs)
|
WebBook.getChapterList(scope, bookSource, book, runPreUpdateJs)
|
||||||
.onSuccess(IO) {
|
.onSuccess(IO) {
|
||||||
if (inBookshelf) {
|
if (inBookshelf) {
|
||||||
book.save()
|
appDb.bookDao.replace(oldBook, book)
|
||||||
/**
|
/**
|
||||||
* runPreUpdateJs 有可能会修改 book 的 bookUrl
|
* runPreUpdateJs 有可能会修改 book 的 bookUrl
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package io.legato.kazusa.ui.book.info.edit
|
package io.legato.kazusa.ui.book.info.edit
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.legato.kazusa.base.BaseViewModel
|
import io.legato.kazusa.base.BaseViewModel
|
||||||
|
import io.legato.kazusa.constant.AppLog
|
||||||
import io.legato.kazusa.data.appDb
|
import io.legato.kazusa.data.appDb
|
||||||
import io.legato.kazusa.data.entities.Book
|
import io.legato.kazusa.data.entities.Book
|
||||||
import io.legato.kazusa.model.ReadBook
|
import io.legato.kazusa.model.ReadBook
|
||||||
@@ -28,6 +30,12 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
|
|||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success?.invoke()
|
success?.invoke()
|
||||||
|
}.onError {
|
||||||
|
if (it is SQLiteConstraintException) {
|
||||||
|
AppLog.put("书籍信息保存失败,存在相同书名作者书籍\n$it", it, true)
|
||||||
|
} else {
|
||||||
|
AppLog.put("书籍信息保存失败\n$it", it, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
|
|||||||
if (oldBook.bookUrl == book.bookUrl) {
|
if (oldBook.bookUrl == book.bookUrl) {
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
} else {
|
} else {
|
||||||
appDb.bookDao.insert(book)
|
appDb.bookDao.replace(oldBook, book)
|
||||||
BookHelp.updateCacheFolder(oldBook, book)
|
BookHelp.updateCacheFolder(oldBook, book)
|
||||||
}
|
}
|
||||||
appDb.bookChapterDao.delByBook(oldBook.bookUrl)
|
appDb.bookChapterDao.delByBook(oldBook.bookUrl)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
@@ -19,6 +18,7 @@ import androidx.activity.addCallback
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.doOnPreDraw
|
import androidx.core.view.doOnPreDraw
|
||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
import androidx.core.view.size
|
import androidx.core.view.size
|
||||||
@@ -906,10 +906,11 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
ReadBook.bookSource?.bookSourceUrl?.let {
|
ReadBook.bookSource?.bookSourceUrl?.let {
|
||||||
scopes.add(it)
|
scopes.add(it)
|
||||||
}
|
}
|
||||||
|
val text = selectedText.lineSequence().map { it.trim() }.joinToString("\n")
|
||||||
replaceActivity.launch(
|
replaceActivity.launch(
|
||||||
ReplaceEditActivity.startIntent(
|
ReplaceEditActivity.startIntent(
|
||||||
this,
|
this,
|
||||||
pattern = selectedText,
|
pattern = text,
|
||||||
scope = scopes.joinToString(";")
|
scope = scopes.joinToString(";")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1421,7 +1422,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
value = src
|
value = src
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewModel.saveImage(src, Uri.parse(path))
|
viewModel.saveImage(src, path.toUri())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
if (oldBook.bookUrl == book.bookUrl) {
|
if (oldBook.bookUrl == book.bookUrl) {
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
} else {
|
} else {
|
||||||
appDb.bookDao.insert(book)
|
appDb.bookDao.replace(oldBook, book)
|
||||||
BookHelp.updateCacheFolder(oldBook, book)
|
BookHelp.updateCacheFolder(oldBook, book)
|
||||||
}
|
}
|
||||||
appDb.bookChapterDao.delByBook(oldBook.bookUrl)
|
appDb.bookChapterDao.delByBook(oldBook.bookUrl)
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
package io.legato.kazusa.ui.book.read.config
|
package io.legato.kazusa.ui.book.read.config
|
||||||
|
|
||||||
//import io.legado.app.lib.theme.bottomBackground
|
|
||||||
//import io.legado.app.lib.theme.getPrimaryTextColor
|
|
||||||
//import io.legado.app.lib.theme.getSecondaryTextColor
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||||
@@ -22,12 +20,14 @@ import io.legato.kazusa.databinding.DialogEditTextBinding
|
|||||||
import io.legato.kazusa.databinding.DialogReadBgTextBinding
|
import io.legato.kazusa.databinding.DialogReadBgTextBinding
|
||||||
import io.legato.kazusa.databinding.ItemBgImageBinding
|
import io.legato.kazusa.databinding.ItemBgImageBinding
|
||||||
import io.legato.kazusa.help.DefaultData
|
import io.legato.kazusa.help.DefaultData
|
||||||
|
import io.legato.kazusa.help.book.isImage
|
||||||
import io.legato.kazusa.help.config.ReadBookConfig
|
import io.legato.kazusa.help.config.ReadBookConfig
|
||||||
import io.legato.kazusa.help.http.newCallResponseBody
|
import io.legato.kazusa.help.http.newCallResponseBody
|
||||||
import io.legato.kazusa.help.http.okHttpClient
|
import io.legato.kazusa.help.http.okHttpClient
|
||||||
import io.legato.kazusa.lib.dialogs.SelectItem
|
import io.legato.kazusa.lib.dialogs.SelectItem
|
||||||
import io.legato.kazusa.lib.dialogs.alert
|
import io.legato.kazusa.lib.dialogs.alert
|
||||||
import io.legato.kazusa.lib.dialogs.selector
|
import io.legato.kazusa.lib.dialogs.selector
|
||||||
|
import io.legato.kazusa.model.ReadBook
|
||||||
import io.legato.kazusa.ui.book.read.ReadBookActivity
|
import io.legato.kazusa.ui.book.read.ReadBookActivity
|
||||||
import io.legato.kazusa.ui.file.HandleFileContract
|
import io.legato.kazusa.ui.file.HandleFileContract
|
||||||
import io.legato.kazusa.utils.FileUtils
|
import io.legato.kazusa.utils.FileUtils
|
||||||
@@ -119,6 +119,7 @@ class BgTextConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_bg
|
|||||||
tvName.text = getString(R.string.select_image)
|
tvName.text = getString(R.string.select_image)
|
||||||
ivBg.setImageResource(R.drawable.ic_image)
|
ivBg.setImageResource(R.drawable.ic_image)
|
||||||
ivBg.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)
|
ivBg.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)
|
||||||
|
swUnderline.isGone = ReadBook.book?.isImage == true
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
selectBgImage.launch()
|
selectBgImage.launch()
|
||||||
}
|
}
|
||||||
@@ -184,8 +185,8 @@ class BgTextConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_bg
|
|||||||
}
|
}
|
||||||
binding.tvBgColor.setOnClickListener {
|
binding.tvBgColor.setOnClickListener {
|
||||||
val bgColor =
|
val bgColor =
|
||||||
if (curBgType() == 0) Color.parseColor(curBgStr())
|
if (curBgType() == 0) curBgStr().toColorInt()
|
||||||
else Color.parseColor("#015A86")
|
else "#015A86".toColorInt()
|
||||||
ColorPickerDialog.newBuilder()
|
ColorPickerDialog.newBuilder()
|
||||||
.setColor(bgColor)
|
.setColor(bgColor)
|
||||||
.setShowAlphaSlider(false)
|
.setShowAlphaSlider(false)
|
||||||
@@ -355,7 +356,7 @@ class BgTextConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_bg
|
|||||||
|
|
||||||
private fun importConfig(byteArray: ByteArray) {
|
private fun importConfig(byteArray: ByteArray) {
|
||||||
execute {
|
execute {
|
||||||
ReadBookConfig.import(byteArray).getOrThrow()
|
ReadBookConfig.import(byteArray)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
ReadBookConfig.durConfig = it
|
ReadBookConfig.durConfig = it
|
||||||
postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))
|
postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class ClickActionConfigDialog : BaseDialogFragment(R.layout.dialog_click_action_
|
|||||||
Pair(9, getString(R.string.replace_state_change)),
|
Pair(9, getString(R.string.replace_state_change)),
|
||||||
Pair(10, getString(R.string.chapter_list)),
|
Pair(10, getString(R.string.chapter_list)),
|
||||||
Pair(11, getString(R.string.search_content)),
|
Pair(11, getString(R.string.search_content)),
|
||||||
Pair(12, getString(R.string.sync_book_progress_t))
|
Pair(12, getString(R.string.sync_book_progress_t)),
|
||||||
|
Pair(13, getString(R.string.read_aloud_pause_resume))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ class MoreConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_more_conf
|
|||||||
ReadBook.callBack?.upPageAnim(true)
|
ReadBook.callBack?.upPageAnim(true)
|
||||||
ReadBook.loadContent(false)
|
ReadBook.loadContent(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreferKey.paddingDisplayCutouts -> {
|
||||||
|
postEvent(EventBus.UP_CONFIG, arrayListOf(2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import io.legato.kazusa.utils.themeColor
|
|||||||
/**
|
/**
|
||||||
* 自动翻页
|
* 自动翻页
|
||||||
*/
|
*/
|
||||||
class AutoPager(
|
class AutoPager(private val readView: ReadView) : Runnable {
|
||||||
private val readView: ReadView) {
|
|
||||||
private var progress = 0
|
private var progress = 0
|
||||||
var isRunning = false
|
var isRunning = false
|
||||||
private set
|
private set
|
||||||
private var isPausing = false
|
private var isPausing = false
|
||||||
|
private var isEInkMode = false
|
||||||
private var scrollOffsetRemain = 0.0
|
private var scrollOffsetRemain = 0.0
|
||||||
private var scrollOffset = 0
|
private var scrollOffset = 0
|
||||||
private var lastTimeMillis = 0L
|
private var lastTimeMillis = 0L
|
||||||
@@ -29,11 +29,16 @@ class AutoPager(
|
|||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
isRunning = true
|
isRunning = true
|
||||||
|
isEInkMode = AppConfig.isEInkMode
|
||||||
|
readView.curPage.upSelectAble(false)
|
||||||
|
if (isEInkMode) {
|
||||||
|
readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)
|
||||||
|
} else {
|
||||||
paint.color = readView.context.themeColor(androidx.appcompat.R.attr.colorPrimary)
|
paint.color = readView.context.themeColor(androidx.appcompat.R.attr.colorPrimary)
|
||||||
lastTimeMillis = SystemClock.uptimeMillis()
|
lastTimeMillis = SystemClock.uptimeMillis()
|
||||||
readView.curPage.upSelectAble(false)
|
|
||||||
readView.invalidate()
|
readView.invalidate()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
@@ -41,6 +46,8 @@ class AutoPager(
|
|||||||
}
|
}
|
||||||
isRunning = false
|
isRunning = false
|
||||||
isPausing = false
|
isPausing = false
|
||||||
|
isEInkMode = false
|
||||||
|
readView.removeCallbacks(this)
|
||||||
readView.curPage.upSelectAble(AppConfig.textSelectAble)
|
readView.curPage.upSelectAble(AppConfig.textSelectAble)
|
||||||
readView.invalidate()
|
readView.invalidate()
|
||||||
reset()
|
reset()
|
||||||
@@ -52,6 +59,7 @@ class AutoPager(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isPausing = true
|
isPausing = true
|
||||||
|
readView.removeCallbacks(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume() {
|
fun resume() {
|
||||||
@@ -59,16 +67,25 @@ class AutoPager(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isPausing = false
|
isPausing = false
|
||||||
|
if (isEInkMode) {
|
||||||
|
readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)
|
||||||
|
} else {
|
||||||
lastTimeMillis = SystemClock.uptimeMillis()
|
lastTimeMillis = SystemClock.uptimeMillis()
|
||||||
readView.invalidate()
|
readView.invalidate()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
|
if (isEInkMode) {
|
||||||
|
readView.removeCallbacks(this)
|
||||||
|
readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)
|
||||||
|
} else {
|
||||||
progress = 0
|
progress = 0
|
||||||
scrollOffsetRemain = 0.0
|
scrollOffsetRemain = 0.0
|
||||||
scrollOffset = 0
|
scrollOffset = 0
|
||||||
canvasRecorder.invalidate()
|
canvasRecorder.invalidate()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun upRecorder() {
|
fun upRecorder() {
|
||||||
canvasRecorder.recycle()
|
canvasRecorder.recycle()
|
||||||
@@ -76,12 +93,15 @@ class AutoPager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onDraw(canvas: Canvas) {
|
fun onDraw(canvas: Canvas) {
|
||||||
if (!isRunning) {
|
if (!isRunning || isEInkMode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readView.isScroll) {
|
if (readView.isScroll) {
|
||||||
if (!isPausing) readView.curPage.scroll(-scrollOffset)
|
if (!isPausing) {
|
||||||
|
readView.curPage.scroll(-scrollOffset)
|
||||||
|
scrollOffset = 0
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val bottom = progress
|
val bottom = progress
|
||||||
val width = readView.width
|
val width = readView.width
|
||||||
@@ -104,7 +124,7 @@ class AutoPager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun computeOffset() {
|
fun computeOffset() {
|
||||||
if (!isRunning || isPausing) {
|
if (!isRunning || isPausing || isEInkMode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,4 +152,15 @@ class AutoPager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
if (!isRunning || isPausing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!readView.fillPage(PageDirection.NEXT)) {
|
||||||
|
stop()
|
||||||
|
} else {
|
||||||
|
readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,15 +2,20 @@ package io.legato.kazusa.ui.book.read.page
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import io.legato.kazusa.R
|
import io.legato.kazusa.R
|
||||||
import io.legato.kazusa.constant.AppConst.timeFormat
|
import io.legato.kazusa.constant.AppConst.timeFormat
|
||||||
import io.legato.kazusa.data.entities.Bookmark
|
import io.legato.kazusa.data.entities.Bookmark
|
||||||
import io.legato.kazusa.databinding.ViewBookPageBinding
|
import io.legato.kazusa.databinding.ViewBookPageBinding
|
||||||
|
import io.legato.kazusa.help.config.AppConfig
|
||||||
import io.legato.kazusa.help.config.ReadBookConfig
|
import io.legato.kazusa.help.config.ReadBookConfig
|
||||||
import io.legato.kazusa.help.config.ReadTipConfig
|
import io.legato.kazusa.help.config.ReadTipConfig
|
||||||
import io.legato.kazusa.model.ReadBook
|
import io.legato.kazusa.model.ReadBook
|
||||||
@@ -25,6 +30,7 @@ import io.legato.kazusa.utils.applyNavigationBarPadding
|
|||||||
import io.legato.kazusa.utils.applyStatusBarPadding
|
import io.legato.kazusa.utils.applyStatusBarPadding
|
||||||
import io.legato.kazusa.utils.dpToPx
|
import io.legato.kazusa.utils.dpToPx
|
||||||
import io.legato.kazusa.utils.gone
|
import io.legato.kazusa.utils.gone
|
||||||
|
import io.legato.kazusa.utils.setOnApplyWindowInsetsListenerCompat
|
||||||
import io.legato.kazusa.utils.setTextIfNotEqual
|
import io.legato.kazusa.utils.setTextIfNotEqual
|
||||||
import io.legato.kazusa.utils.statusBarHeight
|
import io.legato.kazusa.utils.statusBarHeight
|
||||||
import splitties.views.backgroundColor
|
import splitties.views.backgroundColor
|
||||||
@@ -56,7 +62,7 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
get() {
|
get() {
|
||||||
val h1 = if (binding.vwStatusBar.isGone) 0 else binding.vwStatusBar.height
|
val h1 = if (binding.vwStatusBar.isGone) 0 else binding.vwStatusBar.height
|
||||||
val h2 = if (binding.llHeader.isGone) 0 else binding.llHeader.height
|
val h2 = if (binding.llHeader.isGone) 0 else binding.llHeader.height
|
||||||
return h1 + h2
|
return h1 + h2 + binding.vwRoot.paddingTop
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -94,6 +100,7 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
vwBottomDivider.backgroundColor = tipDividerColor
|
vwBottomDivider.backgroundColor = tipDividerColor
|
||||||
upStatusBar()
|
upStatusBar()
|
||||||
upNavigationBar()
|
upNavigationBar()
|
||||||
|
upPaddingDisplayCutouts()
|
||||||
llHeader.setPadding(
|
llHeader.setPadding(
|
||||||
it.headerPaddingLeft.dpToPx(),
|
it.headerPaddingLeft.dpToPx(),
|
||||||
it.headerPaddingTop.dpToPx(),
|
it.headerPaddingTop.dpToPx(),
|
||||||
@@ -125,6 +132,24 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
binding.vwNavigationBar.isGone = ReadBookConfig.hideNavigationBar
|
binding.vwNavigationBar.isGone = ReadBookConfig.hideNavigationBar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun upPaddingDisplayCutouts() {
|
||||||
|
if (AppConfig.paddingDisplayCutouts) {
|
||||||
|
binding.vwRoot.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->
|
||||||
|
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||||
|
binding.vwRoot.setPadding(
|
||||||
|
insets.left,
|
||||||
|
if (binding.vwStatusBar.isGone) insets.top else 0,
|
||||||
|
insets.right,
|
||||||
|
insets.bottom
|
||||||
|
)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.vwRoot, null)
|
||||||
|
binding.vwRoot.setPadding(0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新阅读信息
|
* 更新阅读信息
|
||||||
*/
|
*/
|
||||||
@@ -239,8 +264,12 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
* 更新背景
|
* 更新背景
|
||||||
*/
|
*/
|
||||||
fun upBg() {
|
fun upBg() {
|
||||||
binding.vwRoot.backgroundColor = ReadBookConfig.bgMeanColor
|
binding.vwRoot.background = LayerDrawable(
|
||||||
binding.vwBg.background = ReadBookConfig.bg
|
arrayOf(
|
||||||
|
ReadBookConfig.bgMeanColor.toDrawable(),
|
||||||
|
ReadBookConfig.bg
|
||||||
|
)
|
||||||
|
)
|
||||||
upBgAlpha()
|
upBgAlpha()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +277,8 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
* 更新背景透明度
|
* 更新背景透明度
|
||||||
*/
|
*/
|
||||||
fun upBgAlpha() {
|
fun upBgAlpha() {
|
||||||
binding.vwBg.alpha = ReadBookConfig.bgAlpha / 100f
|
ReadBookConfig.bg?.alpha = (ReadBookConfig.bgAlpha / 100f * 255).toInt()
|
||||||
|
binding.vwRoot.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.legato.kazusa.help.config.AppConfig
|
|||||||
import io.legato.kazusa.help.config.ReadBookConfig
|
import io.legato.kazusa.help.config.ReadBookConfig
|
||||||
import io.legato.kazusa.model.ReadAloud
|
import io.legato.kazusa.model.ReadAloud
|
||||||
import io.legato.kazusa.model.ReadBook
|
import io.legato.kazusa.model.ReadBook
|
||||||
|
import io.legato.kazusa.service.BaseReadAloudService
|
||||||
import io.legato.kazusa.ui.book.read.ContentEditDialog
|
import io.legato.kazusa.ui.book.read.ContentEditDialog
|
||||||
import io.legato.kazusa.ui.book.read.page.api.DataSource
|
import io.legato.kazusa.ui.book.read.page.api.DataSource
|
||||||
import io.legato.kazusa.ui.book.read.page.delegate.CoverPageDelegate
|
import io.legato.kazusa.ui.book.read.page.delegate.CoverPageDelegate
|
||||||
@@ -456,6 +457,13 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
{ progress -> callBack.sureNewProgress(progress) },
|
{ progress -> callBack.sureNewProgress(progress) },
|
||||||
{ context.longToastOnUi(context.getString(R.string.upload_book_success)) },
|
{ context.longToastOnUi(context.getString(R.string.upload_book_success)) },
|
||||||
{ context.longToastOnUi(context.getString(R.string.sync_book_progress_success)) })
|
{ context.longToastOnUi(context.getString(R.string.sync_book_progress_success)) })
|
||||||
|
13 -> {
|
||||||
|
if (BaseReadAloudService.isPlay()) {
|
||||||
|
ReadAloud.pause(context)
|
||||||
|
} else {
|
||||||
|
ReadAloud.resume(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,15 @@ data class TextLine(
|
|||||||
columns[i].draw(view, canvas)
|
columns[i].draw(view, canvas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 墨水屏模式下的朗读和搜索下划线
|
||||||
|
if (AppConfig.isEInkMode && (isReadAloud || searchResultColumnCount > 0)) {
|
||||||
|
val underlinePaint = PaintPool.obtain()
|
||||||
|
underlinePaint.set(ChapterProvider.contentPaint)
|
||||||
|
underlinePaint.strokeWidth = 1.dpToPx().toFloat()
|
||||||
|
val lineY = height - 1.dpToPx()
|
||||||
|
canvas.drawLine(lineStart + indentWidth, lineY, lineEnd, lineY, underlinePaint)
|
||||||
|
PaintPool.recycle(underlinePaint)
|
||||||
|
}
|
||||||
if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) {
|
if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) {
|
||||||
drawUnderline(canvas)
|
drawUnderline(canvas)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import io.legato.kazusa.utils.postEvent
|
|||||||
import io.legato.kazusa.utils.spToPx
|
import io.legato.kazusa.utils.spToPx
|
||||||
import io.legato.kazusa.utils.splitNotBlank
|
import io.legato.kazusa.utils.splitNotBlank
|
||||||
import io.legato.kazusa.utils.textHeight
|
import io.legato.kazusa.utils.textHeight
|
||||||
|
import io.legato.kazusa.utils.toastOnUi
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
@@ -1001,7 +1002,17 @@ object ChapterProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewWidth > 0 && viewHeight > 0) {
|
if (viewWidth <= 0 || viewHeight <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleRect.set(
|
||||||
|
paddingLeft.toFloat(),
|
||||||
|
paddingTop.toFloat(),
|
||||||
|
visibleRight.toFloat(),
|
||||||
|
visibleBottom.toFloat()
|
||||||
|
)
|
||||||
|
|
||||||
paddingLeft = ReadBookConfig.paddingLeft.dpToPx()
|
paddingLeft = ReadBookConfig.paddingLeft.dpToPx()
|
||||||
paddingTop = ReadBookConfig.paddingTop.dpToPx()
|
paddingTop = ReadBookConfig.paddingTop.dpToPx()
|
||||||
paddingRight = ReadBookConfig.paddingRight.dpToPx()
|
paddingRight = ReadBookConfig.paddingRight.dpToPx()
|
||||||
@@ -1015,14 +1026,23 @@ object ChapterProvider {
|
|||||||
visibleHeight = viewHeight - paddingTop - paddingBottom
|
visibleHeight = viewHeight - paddingTop - paddingBottom
|
||||||
visibleRight = viewWidth - paddingRight
|
visibleRight = viewWidth - paddingRight
|
||||||
visibleBottom = paddingTop + visibleHeight
|
visibleBottom = paddingTop + visibleHeight
|
||||||
}
|
|
||||||
|
|
||||||
|
if (paddingLeft >= visibleRight || paddingTop >= visibleBottom) {
|
||||||
|
appCtx.toastOnUi("边距设置过大,请重新设置")
|
||||||
|
visibleRect.set(
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
viewWidth.toFloat(),
|
||||||
|
viewHeight.toFloat()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
visibleRect.set(
|
visibleRect.set(
|
||||||
paddingLeft.toFloat(),
|
paddingLeft.toFloat(),
|
||||||
paddingTop.toFloat(),
|
paddingTop.toFloat(),
|
||||||
visibleRight.toFloat(),
|
visibleRight.toFloat(),
|
||||||
visibleBottom.toFloat()
|
visibleBottom.toFloat()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import io.legato.kazusa.ui.book.read.page.entities.TextChapter
|
|||||||
import io.legato.kazusa.ui.book.read.page.entities.TextLine
|
import io.legato.kazusa.ui.book.read.page.entities.TextLine
|
||||||
import io.legato.kazusa.ui.book.read.page.entities.TextPage
|
import io.legato.kazusa.ui.book.read.page.entities.TextPage
|
||||||
import io.legato.kazusa.ui.book.read.page.entities.column.ImageColumn
|
import io.legato.kazusa.ui.book.read.page.entities.column.ImageColumn
|
||||||
import io.legato.kazusa.ui.book.read.page.entities.column.ReviewColumn
|
|
||||||
import io.legato.kazusa.ui.book.read.page.entities.column.TextColumn
|
import io.legato.kazusa.ui.book.read.page.entities.column.TextColumn
|
||||||
import io.legato.kazusa.utils.dpToPx
|
import io.legato.kazusa.utils.dpToPx
|
||||||
import io.legato.kazusa.utils.fastSum
|
import io.legato.kazusa.utils.fastSum
|
||||||
@@ -214,13 +213,13 @@ class TextChapterLayout(
|
|||||||
emptyContent = contents.isEmpty(),
|
emptyContent = contents.isEmpty(),
|
||||||
isVolumeTitle = bookChapter.isVolume
|
isVolumeTitle = bookChapter.isVolume
|
||||||
)
|
)
|
||||||
}
|
|
||||||
pendingTextPage.lines.last().isParagraphEnd = true
|
pendingTextPage.lines.last().isParagraphEnd = true
|
||||||
stringBuilder.append("\n")
|
stringBuilder.append("\n")
|
||||||
|
}
|
||||||
durY += titleBottomSpacing
|
durY += titleBottomSpacing
|
||||||
|
|
||||||
// 如果是单图模式且当前页有内容,强制分页
|
// 如果是单图模式且当前页有内容,强制分页
|
||||||
if (isSingleImageStyle && pendingTextPage.lines.isNotEmpty()) {
|
if (isSingleImageStyle && pendingTextPage.lines.isNotEmpty() && contents.isNotEmpty()) {
|
||||||
prepareNextPageIfNeed()
|
prepareNextPageIfNeed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,13 +707,13 @@ class TextChapterLayout(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLineEnd && char == ChapterProvider.reviewChar -> {
|
// isLineEnd && char == ChapterProvider.reviewChar -> {
|
||||||
ReviewColumn(
|
// ReviewColumn(
|
||||||
start = absStartX + xStart,
|
// start = absStartX + xStart,
|
||||||
end = absStartX + xEnd,
|
// end = absStartX + xEnd,
|
||||||
count = 100
|
// count = 100
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
TextColumn(
|
TextColumn(
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ import androidx.activity.viewModels
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
|
||||||
import com.google.android.material.search.SearchBar
|
import com.google.android.material.search.SearchBar
|
||||||
import com.google.android.material.search.SearchView
|
import com.google.android.material.search.SearchView
|
||||||
import io.legato.kazusa.R
|
import io.legato.kazusa.R
|
||||||
@@ -45,6 +46,7 @@ import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
|||||||
import io.legato.kazusa.utils.visible
|
import io.legato.kazusa.utils.visible
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.awaitCancellation
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.conflate
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
@@ -183,8 +185,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
|
|||||||
|
|
||||||
binding.appBar.post {
|
binding.appBar.post {
|
||||||
val maxPaddingTop = binding.appBar.paddingTop
|
val maxPaddingTop = binding.appBar.paddingTop
|
||||||
binding.appBar.addOnOffsetChangedListener(
|
binding.appBar.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
|
||||||
AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
|
|
||||||
val totalScrollRange = appBarLayout.totalScrollRange
|
val totalScrollRange = appBarLayout.totalScrollRange
|
||||||
val scrollRatio =
|
val scrollRatio =
|
||||||
(1f - (abs(verticalOffset).toFloat() / totalScrollRange)).coerceIn(0f, 1f)
|
(1f - (abs(verticalOffset).toFloat() / totalScrollRange)).coerceIn(0f, 1f)
|
||||||
@@ -197,7 +198,6 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
|
|||||||
appBarLayout.paddingBottom
|
appBarLayout.paddingBottom
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBar.setOnMenuItemClickListener { item ->
|
searchBar.setOnMenuItemClickListener { item ->
|
||||||
@@ -343,6 +343,16 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
|
|||||||
groups = it
|
groups = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
viewModel.resume()
|
||||||
|
try {
|
||||||
|
awaitCancellation()
|
||||||
|
} finally {
|
||||||
|
viewModel.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -123,6 +123,14 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
searchModel.cancelSearch()
|
searchModel.cancelSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
searchModel.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
searchModel.resume()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存搜索关键字
|
* 保存搜索关键字
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.legato.kazusa.ui.book.searchContent
|
|||||||
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
|
import io.legato.kazusa.help.config.AppConfig
|
||||||
|
|
||||||
data class SearchResult(
|
data class SearchResult(
|
||||||
val resultCount: Int = 0,
|
val resultCount: Int = 0,
|
||||||
@@ -22,19 +23,34 @@ data class SearchResult(
|
|||||||
val leftString = resultText.substring(0, queryIndexInSurrounding)
|
val leftString = resultText.substring(0, queryIndexInSurrounding)
|
||||||
val rightString =
|
val rightString =
|
||||||
resultText.substring(queryIndexInSurrounding + query.length, resultText.length)
|
resultText.substring(queryIndexInSurrounding + query.length, resultText.length)
|
||||||
val html = buildString {
|
// 检查是否为墨水屏模式
|
||||||
|
val html = if (AppConfig.isEInkMode) {
|
||||||
|
// 墨水屏模式:使用下划线
|
||||||
|
buildString {
|
||||||
|
append("<u>${chapterTitle}</u>")
|
||||||
|
append("<br>")
|
||||||
|
append(leftString)
|
||||||
|
append("<u>${query}</u>")
|
||||||
|
append(rightString)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 普通模式:使用颜色
|
||||||
|
buildString {
|
||||||
append(chapterTitle.colorTextForHtml(accentColor))
|
append(chapterTitle.colorTextForHtml(accentColor))
|
||||||
append("<br>")
|
append("<br>")
|
||||||
append(leftString.colorTextForHtml(textColor))
|
append(leftString.colorTextForHtml(textColor))
|
||||||
append(query.colorTextForHtml(accentColor))
|
append(query.colorTextForHtml(accentColor))
|
||||||
append(rightString.colorTextForHtml(textColor))
|
append(rightString.colorTextForHtml(textColor))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
} else {
|
} else {
|
||||||
HtmlCompat.fromHtml(
|
val html = if (AppConfig.isEInkMode) {
|
||||||
resultText.colorTextForHtml(textColor),
|
resultText
|
||||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
} else {
|
||||||
)
|
resultText.colorTextForHtml(textColor)
|
||||||
|
}
|
||||||
|
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,19 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
|
|||||||
tag = it
|
tag = it
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
setupTabLayoutMode(it.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//根据已启用词典数动态选取布局
|
||||||
|
private fun setupTabLayoutMode(dictCount: Int) {
|
||||||
|
if (dictCount <= 4) {
|
||||||
|
binding.tabLayout.tabMode = TabLayout.MODE_FIXED
|
||||||
|
binding.tabLayout.tabGravity = TabLayout.GRAVITY_FILL
|
||||||
|
} else {
|
||||||
|
binding.tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
|
||||||
|
binding.tabLayout.tabGravity = TabLayout.GRAVITY_CENTER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,11 @@ class SourceLoginActivity : VMBaseActivity<ActivitySourceLoginBinding, SourceLog
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
viewModel.initData(intent) { source ->
|
viewModel.initData(intent, success = { source ->
|
||||||
initView(source)
|
initView(source)
|
||||||
}
|
}, error = {
|
||||||
|
finish()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView(source: BaseSource) {
|
private fun initView(source: BaseSource) {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package io.legato.kazusa.ui.login
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import com.script.rhino.runScriptWithContext
|
||||||
import io.legato.kazusa.base.BaseViewModel
|
import io.legato.kazusa.base.BaseViewModel
|
||||||
|
import io.legato.kazusa.constant.AppLog
|
||||||
import io.legato.kazusa.data.appDb
|
import io.legato.kazusa.data.appDb
|
||||||
import io.legato.kazusa.data.entities.BaseSource
|
import io.legato.kazusa.data.entities.BaseSource
|
||||||
import io.legato.kazusa.exception.NoStackTraceException
|
import io.legato.kazusa.exception.NoStackTraceException
|
||||||
import com.script.rhino.runScriptWithContext
|
|
||||||
import io.legato.kazusa.utils.toastOnUi
|
import io.legato.kazusa.utils.toastOnUi
|
||||||
|
|
||||||
class SourceLoginViewModel(application: Application) : BaseViewModel(application) {
|
class SourceLoginViewModel(application: Application) : BaseViewModel(application) {
|
||||||
@@ -14,7 +15,7 @@ class SourceLoginViewModel(application: Application) : BaseViewModel(application
|
|||||||
var source: BaseSource? = null
|
var source: BaseSource? = null
|
||||||
var headerMap: Map<String, String> = emptyMap()
|
var headerMap: Map<String, String> = emptyMap()
|
||||||
|
|
||||||
fun initData(intent: Intent, success: (bookSource: BaseSource) -> Unit) {
|
fun initData(intent: Intent, success: (bookSource: BaseSource) -> Unit, error: () -> Unit) {
|
||||||
execute {
|
execute {
|
||||||
val sourceKey = intent.getStringExtra("key")
|
val sourceKey = intent.getStringExtra("key")
|
||||||
?: throw NoStackTraceException("没有参数")
|
?: throw NoStackTraceException("没有参数")
|
||||||
@@ -33,6 +34,9 @@ class SourceLoginViewModel(application: Application) : BaseViewModel(application
|
|||||||
} else {
|
} else {
|
||||||
context.toastOnUi("未找到书源")
|
context.toastOnUi("未找到书源")
|
||||||
}
|
}
|
||||||
|
}.onError {
|
||||||
|
error.invoke()
|
||||||
|
AppLog.put("登录 UI 初始化失败\n$it", it, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
if (book.bookUrl == bookUrl) {
|
if (book.bookUrl == bookUrl) {
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
} else {
|
} else {
|
||||||
appDb.bookDao.insert(book)
|
appDb.bookDao.replace(oldBook, book)
|
||||||
BookHelp.updateCacheFolder(oldBook, book)
|
BookHelp.updateCacheFolder(oldBook, book)
|
||||||
}
|
}
|
||||||
appDb.bookChapterDao.delByBook(bookUrl)
|
appDb.bookChapterDao.delByBook(bookUrl)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package io.legato.kazusa.ui.replace
|
package io.legato.kazusa.ui.replace
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -25,8 +24,6 @@ import io.legato.kazusa.help.DirectLinkUpload
|
|||||||
import io.legato.kazusa.help.book.ContentProcessor
|
import io.legato.kazusa.help.book.ContentProcessor
|
||||||
import io.legato.kazusa.help.coroutine.Coroutine
|
import io.legato.kazusa.help.coroutine.Coroutine
|
||||||
import io.legato.kazusa.lib.dialogs.alert
|
import io.legato.kazusa.lib.dialogs.alert
|
||||||
//import io.legado.app.lib.theme.primaryColor
|
|
||||||
//import io.legado.app.lib.theme.primaryTextColor
|
|
||||||
import io.legato.kazusa.ui.association.ImportReplaceRuleDialog
|
import io.legato.kazusa.ui.association.ImportReplaceRuleDialog
|
||||||
import io.legato.kazusa.ui.file.HandleFileContract
|
import io.legato.kazusa.ui.file.HandleFileContract
|
||||||
import io.legato.kazusa.ui.qrcode.QrCodeResult
|
import io.legato.kazusa.ui.qrcode.QrCodeResult
|
||||||
@@ -72,7 +69,7 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
|
|||||||
private val searchView: SearchView by lazy {
|
private val searchView: SearchView by lazy {
|
||||||
binding.titleBar.findViewById(R.id.search_view)
|
binding.titleBar.findViewById(R.id.search_view)
|
||||||
}
|
}
|
||||||
private var groups = hashSetOf<String>()
|
private var groups = arrayListOf<String>()
|
||||||
private var groupMenu: SubMenu? = null
|
private var groupMenu: SubMenu? = null
|
||||||
private var replaceRuleFlowJob: Job? = null
|
private var replaceRuleFlowJob: Job? = null
|
||||||
private var dataInit = false
|
private var dataInit = false
|
||||||
@@ -224,7 +221,7 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
|
|||||||
AppLog.put("替换规则管理界面更新数据出错", it)
|
AppLog.put("替换规则管理界面更新数据出错", it)
|
||||||
}.flowOn(IO).conflate().collect {
|
}.flowOn(IO).conflate().collect {
|
||||||
if (dataInit) {
|
if (dataInit) {
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
}
|
}
|
||||||
adapter.setItems(it, adapter.diffItemCallBack)
|
adapter.setItems(it, adapter.diffItemCallBack)
|
||||||
dataInit = true
|
dataInit = true
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import io.legato.kazusa.databinding.ActivityRssSourceBinding
|
|||||||
import io.legato.kazusa.databinding.DialogEditTextBinding
|
import io.legato.kazusa.databinding.DialogEditTextBinding
|
||||||
import io.legato.kazusa.help.DirectLinkUpload
|
import io.legato.kazusa.help.DirectLinkUpload
|
||||||
import io.legato.kazusa.lib.dialogs.alert
|
import io.legato.kazusa.lib.dialogs.alert
|
||||||
//import io.legado.app.lib.theme.primaryColor
|
|
||||||
//import io.legado.app.lib.theme.primaryTextColor
|
|
||||||
import io.legato.kazusa.ui.association.ImportRssSourceDialog
|
import io.legato.kazusa.ui.association.ImportRssSourceDialog
|
||||||
import io.legato.kazusa.ui.file.HandleFileContract
|
import io.legato.kazusa.ui.file.HandleFileContract
|
||||||
import io.legato.kazusa.ui.qrcode.QrCodeResult
|
import io.legato.kazusa.ui.qrcode.QrCodeResult
|
||||||
@@ -70,7 +68,7 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
|
|||||||
binding.titleBar.findViewById(R.id.search_view)
|
binding.titleBar.findViewById(R.id.search_view)
|
||||||
}
|
}
|
||||||
private var sourceFlowJob: Job? = null
|
private var sourceFlowJob: Job? = null
|
||||||
private var groups = hashSetOf<String>()
|
private var groups = arrayListOf<String>()
|
||||||
private var groupMenu: SubMenu? = null
|
private var groupMenu: SubMenu? = null
|
||||||
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
|
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
|
||||||
it ?: return@registerForActivityResult
|
it ?: return@registerForActivityResult
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import androidx.annotation.DrawableRes
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||||
@@ -44,6 +45,7 @@ import io.legato.kazusa.data.entities.Book
|
|||||||
import io.legato.kazusa.help.IntentHelp
|
import io.legato.kazusa.help.IntentHelp
|
||||||
import io.legato.kazusa.help.book.isAudio
|
import io.legato.kazusa.help.book.isAudio
|
||||||
import io.legato.kazusa.help.book.isImage
|
import io.legato.kazusa.help.book.isImage
|
||||||
|
import io.legato.kazusa.help.book.isLocal
|
||||||
import io.legato.kazusa.help.config.AppConfig
|
import io.legato.kazusa.help.config.AppConfig
|
||||||
import io.legato.kazusa.ui.book.audio.AudioPlayActivity
|
import io.legato.kazusa.ui.book.audio.AudioPlayActivity
|
||||||
import io.legato.kazusa.ui.book.manga.ReadMangaActivity
|
import io.legato.kazusa.ui.book.manga.ReadMangaActivity
|
||||||
@@ -68,7 +70,7 @@ fun Context.startActivityForBook(
|
|||||||
) {
|
) {
|
||||||
val cls = when {
|
val cls = when {
|
||||||
book.isAudio -> AudioPlayActivity::class.java
|
book.isAudio -> AudioPlayActivity::class.java
|
||||||
book.isImage && AppConfig.showMangaUi -> ReadMangaActivity::class.java
|
!book.isLocal && book.isImage && AppConfig.showMangaUi -> ReadMangaActivity::class.java
|
||||||
else -> ReadBookActivity::class.java
|
else -> ReadBookActivity::class.java
|
||||||
}
|
}
|
||||||
val intent = Intent(this, cls)
|
val intent = Intent(this, cls)
|
||||||
@@ -323,7 +325,7 @@ fun Context.getClipText(): String? {
|
|||||||
fun Context.sendMail(mail: String) {
|
fun Context.sendMail(mail: String) {
|
||||||
try {
|
try {
|
||||||
val intent = Intent(Intent.ACTION_SENDTO)
|
val intent = Intent(Intent.ACTION_SENDTO)
|
||||||
intent.data = Uri.parse("mailto:$mail")
|
intent.data = "mailto:$mail".toUri()
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.legato.kazusa.R
|
|||||||
import io.legato.kazusa.data.entities.Book
|
import io.legato.kazusa.data.entities.Book
|
||||||
import io.legato.kazusa.help.book.isAudio
|
import io.legato.kazusa.help.book.isAudio
|
||||||
import io.legato.kazusa.help.book.isImage
|
import io.legato.kazusa.help.book.isImage
|
||||||
|
import io.legato.kazusa.help.book.isLocal
|
||||||
import io.legato.kazusa.help.config.AppConfig
|
import io.legato.kazusa.help.config.AppConfig
|
||||||
import io.legato.kazusa.ui.book.audio.AudioPlayActivity
|
import io.legato.kazusa.ui.book.audio.AudioPlayActivity
|
||||||
import io.legato.kazusa.ui.book.manga.ReadMangaActivity
|
import io.legato.kazusa.ui.book.manga.ReadMangaActivity
|
||||||
@@ -93,7 +94,7 @@ fun Fragment.startActivityForBook(
|
|||||||
) {
|
) {
|
||||||
val cls = when {
|
val cls = when {
|
||||||
book.isAudio -> AudioPlayActivity::class.java
|
book.isAudio -> AudioPlayActivity::class.java
|
||||||
book.isImage && AppConfig.showMangaUi -> ReadMangaActivity::class.java
|
!book.isLocal && book.isImage && AppConfig.showMangaUi -> ReadMangaActivity::class.java
|
||||||
else -> ReadBookActivity::class.java
|
else -> ReadBookActivity::class.java
|
||||||
}
|
}
|
||||||
val intent = Intent(requireActivity(), cls)
|
val intent = Intent(requireActivity(), cls)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ object HtmlFormatter {
|
|||||||
private val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
private val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
||||||
private val otherHtmlRegex = "</?[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
private val otherHtmlRegex = "</?[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
|
||||||
private val formatImagePattern = Pattern.compile(
|
private val formatImagePattern = Pattern.compile(
|
||||||
"<img[^>]*\\ssrc\\s*=\\s*\"([^\"{>]*\\{(?:[^{}]|\\{[^}>]+\\})+\\})\"[^>]*>|<img[^>]*\\sdata-[^=>]*=\\s*\"([^\">]*)\"[^>]*>|<img[^>]*\\ssrc\\s*=\\s*\"([^\">]*)\"[^>]*>",
|
"<img[^>]*\\ssrc\\s*=\\s*['\"]([^'\"{>]*\\{(?:[^{}]|\\{[^}>]+\\})+\\})['\"][^>]*>|<img[^>]*\\s(?:data-src|src)\\s*=\\s*['\"]([^'\">]+)['\"][^>]*>|<img[^>]*\\sdata-[^=>]*=\\s*['\"]([^'\">]*)['\"][^>]*>",
|
||||||
Pattern.CASE_INSENSITIVE
|
Pattern.CASE_INSENSITIVE
|
||||||
)
|
)
|
||||||
private val indent1Regex = "\\s*\\n+\\s*".toRegex()
|
private val indent1Regex = "\\s*\\n+\\s*".toRegex()
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package io.legato.kazusa.utils
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
|
|
||||||
val isVivoDevice by lazy {
|
|
||||||
Build.MANUFACTURER.equals("vivo", ignoreCase = true)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M460,600L460,420L400,420L400,360L520,360L520,600L460,600ZM280,880L120,720L280,560L336,618L274,680L680,680L680,520L760,520L760,760L274,760L336,822L280,880ZM200,440L200,200L686,200L624,138L680,80L840,240L680,400L624,342L686,280L280,280L280,440L200,440Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M280,880L120,720L280,560L336,618L274,680L680,680L680,520L760,520L760,760L274,760L336,822L280,880ZM200,440L200,200L686,200L624,138L680,80L840,240L680,400L624,342L686,280L280,280L280,440L200,440Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M560,800L560,720L664,720L537,593L594,536L720,662L720,560L800,560L800,800L560,800ZM216,800L160,744L664,240L560,240L560,160L800,160L800,400L720,400L720,296L216,800ZM367,423L160,216L216,160L423,367L367,423Z" />
|
||||||
|
</vector>
|
||||||
@@ -125,6 +125,20 @@
|
|||||||
android:paddingHorizontal="6dp"
|
android:paddingHorizontal="6dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent">
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<io.legato.kazusa.ui.widget.image.ImageButton
|
||||||
|
android:id="@+id/iv_play_mode"
|
||||||
|
android:layout_width="46dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="@drawable/selector_circle_btn_bg"
|
||||||
|
android:contentDescription="@string/play_mode"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/ic_play_mode_list_end_stop"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/tv_sub_title"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:tint="@color/md_white_1000"
|
||||||
|
tools:ignore="ImageContrastCheck" />
|
||||||
|
|
||||||
<io.legato.kazusa.ui.widget.image.ImageButton
|
<io.legato.kazusa.ui.widget.image.ImageButton
|
||||||
android:id="@+id/iv_timer"
|
android:id="@+id/iv_timer"
|
||||||
android:layout_width="46dp"
|
android:layout_width="46dp"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:stepSize="1.0"
|
android:stepSize="1.0"
|
||||||
android:valueFrom="1.0"
|
android:valueFrom="1.0"
|
||||||
android:valueTo="60.0"
|
android:valueTo="120.0"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -7,11 +7,6 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/vw_bg"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/vw_status_bar"
|
android:id="@+id/vw_status_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -886,7 +886,7 @@
|
|||||||
<string name="pref_cronet_summary">使用Cronet网络组件</string>
|
<string name="pref_cronet_summary">使用Cronet网络组件</string>
|
||||||
<string name="anti_alias">Anti-Aliasing</string>
|
<string name="anti_alias">Anti-Aliasing</string>
|
||||||
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
|
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
|
||||||
<string name="upload_url">上传URL</string>
|
<string name="upload_url">创建分享链接</string>
|
||||||
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
|
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
|
||||||
<string name="sort_by_respondTime">Ordenar por tiempo de respuesta</string>
|
<string name="sort_by_respondTime">Ordenar por tiempo de respuesta</string>
|
||||||
<string name="respondTime">tiempo de respuesta: %1$d ms</string>
|
<string name="respondTime">tiempo de respuesta: %1$d ms</string>
|
||||||
|
|||||||
@@ -889,7 +889,7 @@
|
|||||||
<string name="pref_cronet_summary">使用Cronet网络组件</string>
|
<string name="pref_cronet_summary">使用Cronet网络组件</string>
|
||||||
<string name="anti_alias">Anti-Aliasing</string>
|
<string name="anti_alias">Anti-Aliasing</string>
|
||||||
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
|
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
|
||||||
<string name="upload_url">上传URL</string>
|
<string name="upload_url">创建分享链接</string>
|
||||||
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
|
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
|
||||||
<string name="sort_by_respondTime">Sort by respond time</string>
|
<string name="sort_by_respondTime">Sort by respond time</string>
|
||||||
<string name="respondTime">respondTime: %1$d ms</string>
|
<string name="respondTime">respondTime: %1$d ms</string>
|
||||||
|
|||||||
@@ -887,7 +887,7 @@
|
|||||||
<string name="pref_cronet_summary">Usando componentes de rede Cronet</string>
|
<string name="pref_cronet_summary">Usando componentes de rede Cronet</string>
|
||||||
<string name="anti_alias">Anti-Aliasing</string>
|
<string name="anti_alias">Anti-Aliasing</string>
|
||||||
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
|
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
|
||||||
<string name="upload_url">上传URL</string>
|
<string name="upload_url">创建分享链接</string>
|
||||||
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
|
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
|
||||||
<string name="sort_by_respondTime">Classificar por tempo de resposta</string>
|
<string name="sort_by_respondTime">Classificar por tempo de resposta</string>
|
||||||
<string name="respondTime">tempo de resposta: %1$d ms</string>
|
<string name="respondTime">tempo de resposta: %1$d ms</string>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<item>@string/screen_portrait</item>
|
<item>@string/screen_portrait</item>
|
||||||
<item>@string/screen_landscape</item>
|
<item>@string/screen_landscape</item>
|
||||||
<item>@string/screen_sensor</item>
|
<item>@string/screen_sensor</item>
|
||||||
<item>Đảo ngược màn hình dọc</item>
|
<item>@string/screen_portrait_reversed</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="group_style">
|
<string-array name="group_style">
|
||||||
<item>Nhãn</item>
|
<item>Nhãn</item>
|
||||||
|
|||||||
@@ -1210,4 +1210,5 @@
|
|||||||
<string name="manga_color_filter">滤镜</string>
|
<string name="manga_color_filter">滤镜</string>
|
||||||
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
||||||
<string name="refresh_explore">刷新发现</string>
|
<string name="refresh_explore">刷新发现</string>
|
||||||
|
<string name="padding_display_cutouts">填充刘海区域</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1212,4 +1212,5 @@
|
|||||||
<string name="manga_color_filter">滤镜</string>
|
<string name="manga_color_filter">滤镜</string>
|
||||||
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
||||||
<string name="refresh_explore">刷新发现</string>
|
<string name="refresh_explore">刷新发现</string>
|
||||||
|
<string name="padding_display_cutouts">填充刘海区域</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1234,9 +1234,12 @@
|
|||||||
<string name="list">列表</string>
|
<string name="list">列表</string>
|
||||||
<string name="grid">网格</string>
|
<string name="grid">网格</string>
|
||||||
<string name="descending_order">降序</string>
|
<string name="descending_order">降序</string>
|
||||||
<string name="checking_update">检查更新中...</string>
|
<string name="checking_update">检查更新中…</string>
|
||||||
<string name="search_empty">没有内容哦</string>
|
<string name="search_empty">没有内容哦</string>
|
||||||
<string name="firebase_enable_title">匿名统计</string>
|
<string name="firebase_enable_title">匿名统计</string>
|
||||||
<string name="locate_current">定位至当前</string>
|
<string name="locate_current">定位至当前</string>
|
||||||
|
<string name="padding_display_cutouts">填充刘海区域</string>
|
||||||
|
<string name="system_media_control_compatibility_change">系统媒体控件兼容性更改</string>
|
||||||
|
<string name="system_media_control_compatibility_change_summary">当锁屏不显示系统媒体控件时可以尝试开启,比如 OneUI 7.0或 OriginOS 等</string>
|
||||||
|
<string name="read_aloud_pause_resume">朗读暂停/继续</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1242,5 +1242,9 @@
|
|||||||
<string name="firebase_enable_title">匿名统计</string>
|
<string name="firebase_enable_title">匿名统计</string>
|
||||||
<string name="firebase_enable_summary">允许应用使用Google Firebase进行崩溃和性能统计</string>
|
<string name="firebase_enable_summary">允许应用使用Google Firebase进行崩溃和性能统计</string>
|
||||||
<string name="locate_current">定位至当前</string>
|
<string name="locate_current">定位至当前</string>
|
||||||
|
<string name="padding_display_cutouts">填充刘海区域</string>
|
||||||
|
<string name="system_media_control_compatibility_change">系统媒体控件兼容性更改</string>
|
||||||
|
<string name="system_media_control_compatibility_change_summary">当锁屏不显示系统媒体控件时可以尝试开启,比如 OneUI 7.0或 OriginOS 等</string>
|
||||||
|
<string name="read_aloud_pause_resume">朗读暂停/继续</string>
|
||||||
|
<string name="play_mode">播放模式</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -41,6 +41,13 @@
|
|||||||
android:key="readAloudByPage"
|
android:key="readAloudByPage"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<io.legato.kazusa.lib.prefs.SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="systemMediaControlCompatibilityChange"
|
||||||
|
android:summary="@string/system_media_control_compatibility_change_summary"
|
||||||
|
android:title="@string/system_media_control_compatibility_change"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<io.legato.kazusa.lib.prefs.SwitchPreference
|
<io.legato.kazusa.lib.prefs.SwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:title="@string/stream_read_aloud_audio"
|
android:title="@string/stream_read_aloud_audio"
|
||||||
|
|||||||
@@ -35,6 +35,13 @@
|
|||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:isBottomBackground="true" />
|
app:isBottomBackground="true" />
|
||||||
|
|
||||||
|
<io.legato.kazusa.lib.prefs.SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="hideNavigationBar"
|
||||||
|
android:title="@string/pt_hide_navigation_bar"
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:isBottomBackground="true" />
|
||||||
|
|
||||||
<io.legato.kazusa.lib.prefs.SwitchPreference
|
<io.legato.kazusa.lib.prefs.SwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="readBodyToLh"
|
android:key="readBodyToLh"
|
||||||
@@ -44,8 +51,8 @@
|
|||||||
|
|
||||||
<io.legato.kazusa.lib.prefs.SwitchPreference
|
<io.legato.kazusa.lib.prefs.SwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="hideNavigationBar"
|
android:key="paddingDisplayCutouts"
|
||||||
android:title="@string/pt_hide_navigation_bar"
|
android:title="@string/padding_display_cutouts"
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:isBottomBackground="true" />
|
app:isBottomBackground="true" />
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ material = "1.14.0-alpha04"
|
|||||||
media = "1.7.1"
|
media = "1.7.1"
|
||||||
media3 = "1.8.0"
|
media3 = "1.8.0"
|
||||||
nanoHttpd = "2.3.1"
|
nanoHttpd = "2.3.1"
|
||||||
okhttp = "4.12.0"
|
okhttp = "5.1.0"
|
||||||
palette = "1.0.0"
|
palette = "1.0.0"
|
||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
protobufJavalite = "4.26.1"
|
protobufJavalite = "4.26.1"
|
||||||
|
|||||||
Reference in new issue
Block a user