Compare commits

...

9 Commits

Author SHA1 Message Date
dependabot[bot]
41ecdb9918
Merge 1535b668ae into 5336adc035 2024-05-15 22:56:31 +08:00
dependabot[bot]
5336adc035
Bump pnpm/action-setup from 3 to 4 (#3930)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 3 to 4.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v3...v4)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-15 20:06:20 +08:00
Horis
72ad27ea84 优化 2024-05-14 09:33:56 +08:00
Horis
5eaf2f034f 优化 2024-05-13 10:51:20 +08:00
Horis
2aed021e08 优化
#3929
2024-05-11 19:26:30 +08:00
Horis
06d486c96c 优化 2024-05-11 06:57:32 +08:00
Horis
a35b7737aa 优化 2024-05-10 14:36:01 +08:00
Horis
9edc02f696 优化 2024-05-08 16:33:21 +08:00
youke2580
9d13b353b8
更新 jsHelp.md (#3922) 2024-05-07 13:43:42 +08:00
17 changed files with 81 additions and 83 deletions

View File

@ -27,7 +27,7 @@ jobs:
with:
node-version: 16
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
local.properties
.DS_Store
/build
build/
/captures
.externalNativeBuild
/release

View File

@ -47,7 +47,7 @@ java.searchBook(bookName: string)
* 添加书架
```js
java.addBook(bookUrl)
java.addBook(bookUrl: String)
```
### [AnalyzeUrl](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt) 部分函数
@ -181,6 +181,11 @@ java.get*StringContent(url: String, path: String, charsetName: String): String
java.get*ByteArrayContent(url: String, path: String): ByteArray?
```
* URI编码
```js
java.encodeURI(str: String) //默认enc="UTF-8"
java.encodeURI(str: String, enc: String)
```
* base64
> flags参数可省略默认Base64.NO_WRAP查看[flags参数说明](https://blog.csdn.net/zcmain/article/details/97051870)
@ -384,16 +389,23 @@ source.getVariable()
* 登录头操作
```js
获取登录头
source.getLoginHeader()
获取登录头某一键值
source.getLoginHeaderMap().get(key: String)
保存登录头
source.putLoginHeader(header: String)
清除登录头
source.removeLoginHeader()
```
* 用户登录信息操作
> 使用`登录UI`规则并成功登录阅读自动加密保存登录UI规则中除type为button的信息
```js
login函数获取登录信息
source.getLoginInfo()
login函数获取登录信息键值
source.getLoginInfoMap().get(key: String)
清除登录信息
source.removeLoginInfo()
```
## cookie对象的部分可用函数
@ -402,6 +414,10 @@ source.removeLoginInfo()
cookie.getCookie(url)
获取cookie某一键值
cookie.getKey(url,key)
设置cookie
cookie.setCookie(url,cookie)
替换cookie
cookie.replaceCookie(url,cookie)
删除cookie
cookie.removeCookie(url)
```

View File

@ -284,14 +284,6 @@ data class Book(
}
}
fun getBookSource(): BookSource? {
return appDb.bookSourceDao.getBookSource(origin)
}
fun isLocalModified(): Boolean {
return isLocal && LocalBook.getLastModified(this).getOrDefault(0L) > latestChapterTime
}
fun toSearchBook() = SearchBook(
name = name,
author = author,

View File

@ -73,7 +73,7 @@ object AppWebDav {
WebDav(bookProgressUrl, mAuthorization).makeAsDir()
WebDav(exportsWebDavUrl, mAuthorization).makeAsDir()
WebDav(bgWebDavUrl, mAuthorization).makeAsDir()
val rootBooksUrl = "${rootWebDavUrl}books"
val rootBooksUrl = "${rootWebDavUrl}books/"
defaultBookWebDav = RemoteBookWebDav(rootBooksUrl, mAuthorization)
authorization = mAuthorization
}

View File

@ -5,17 +5,26 @@ package io.legado.app.help.book
import android.net.Uri
import com.script.SimpleBindings
import com.script.rhino.RhinoScriptEngine
import io.legado.app.constant.*
import io.legado.app.constant.AppLog
import io.legado.app.constant.BookSourceType
import io.legado.app.constant.BookType
import io.legado.app.data.appDb
import io.legado.app.data.entities.BaseBook
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
import io.legado.app.utils.*
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.FileDoc
import io.legado.app.utils.exists
import io.legado.app.utils.find
import io.legado.app.utils.inputStream
import io.legado.app.utils.isUri
import io.legado.app.utils.toastOnUi
import splitties.init.appCtx
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.set
val Book.isAudio: Boolean
@ -223,6 +232,14 @@ fun Book.sync(oldBook: Book) {
canUpdate = curBook.canUpdate
}
fun Book.getBookSource(): BookSource? {
return appDb.bookSourceDao.getBookSource(origin)
}
fun Book.isLocalModified(): Boolean {
return isLocal && LocalBook.getLastModified(this).getOrDefault(0L) > latestChapterTime
}
fun Book.isSameNameAuthor(other: Any?): Boolean {
if (other is BaseBook) {
return name == other.name && author == other.author

View File

@ -71,6 +71,8 @@ open class WebDav(
<resourcetype />
</prop>
</propfind>"""
private const val DEFAULT_CONTENT_TYPE = "application/octet-stream"
}
@ -178,13 +180,14 @@ open class WebDav(
//依然是优化支持 caddy 自建的 WebDav ,其目录后缀都为“/”, 所以删除“/”的判定,不然无法获取该目录项
val href = element.findNS("href", ns)[0].text().replace("+", "%2B")
val hrefDecode = URLDecoder.decode(href, "UTF-8")
.removeSuffix("/")
val fileName = hrefDecode.substringAfterLast("/")
val webDavFile: WebDav
try {
val urlName = hrefDecode.ifEmpty {
url.file.replace("/", "")
}
val displayName = element
.findNS("displayname", ns)
.firstOrNull()?.text().orEmpty()
val contentType = element
.findNS("getcontenttype", ns)
.firstOrNull()?.text().orEmpty()
@ -206,7 +209,7 @@ open class WebDav(
webDavFile = WebDavFile(
fullURL,
authorization,
displayName = fileName,
displayName = displayName,
urlName = urlName,
size = size,
contentType = contentType,
@ -303,18 +306,12 @@ open class WebDav(
* 上传文件
*/
@Throws(WebDavException::class)
suspend fun upload(
localPath: String,
contentType: String = "application/octet-stream"
) {
suspend fun upload(localPath: String, contentType: String = DEFAULT_CONTENT_TYPE) {
upload(File(localPath), contentType)
}
@Throws(WebDavException::class)
suspend fun upload(
file: File,
contentType: String = "application/octet-stream"
) {
suspend fun upload(file: File, contentType: String = DEFAULT_CONTENT_TYPE) {
kotlin.runCatching {
withContext(IO) {
if (!file.exists()) throw WebDavException("文件不存在")
@ -335,7 +332,7 @@ open class WebDav(
}
@Throws(WebDavException::class)
suspend fun upload(byteArray: ByteArray, contentType: String) {
suspend fun upload(byteArray: ByteArray, contentType: String = DEFAULT_CONTENT_TYPE) {
// 务必注意RequestBody不要嵌套不然上传时内容可能会被追加多余的文件信息
kotlin.runCatching {
withContext(IO) {
@ -355,7 +352,7 @@ open class WebDav(
}
@Throws(WebDavException::class)
suspend fun upload(uri: Uri, contentType: String) {
suspend fun upload(uri: Uri, contentType: String = DEFAULT_CONTENT_TYPE) {
// 务必注意RequestBody不要嵌套不然上传时内容可能会被追加多余的文件信息
kotlin.runCatching {
withContext(IO) {

View File

@ -11,6 +11,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.getBookSource
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.service.AudioPlayService
import io.legado.app.utils.postEvent

View File

@ -14,10 +14,7 @@ import io.legado.app.model.analyzeRule.CustomUrl
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.readBytes
import kotlinx.coroutines.runBlocking
import splitties.init.appCtx
import java.io.File
class RemoteBookWebDav(
val rootBookUrl: String,
@ -71,20 +68,17 @@ class RemoteBookWebDav(
override suspend fun upload(book: Book) {
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
val localBookUri = Uri.parse(book.bookUrl)
val putUrl = "$rootBookUrl${File.separator}${book.originName}"
val putUrl = "$rootBookUrl${book.originName}"
val webDav = WebDav(putUrl, authorization)
if (localBookUri.isContentScheme()) {
webDav.upload(
byteArray = localBookUri.readBytes(appCtx),
contentType = "application/octet-stream"
)
webDav.upload(localBookUri)
} else {
webDav.upload(localBookUri.path!!)
}
book.origin = BookType.webDavTag + CustomUrl(putUrl)
.putAttribute("serverID", serverID)
.toString()
book.save()
book.update()
}
override suspend fun delete(remoteBookUrl: String) {

View File

@ -96,12 +96,11 @@ class WebService : BaseService() {
if (addressList.any()) {
notificationList.addAll(addressList.map { address -> getString(R.string.http_ip, address.hostAddress, getPort()) })
hostAddress = notificationList.first()
startForegroundNotification()
} else {
hostAddress = getString(R.string.network_connection_unavailable)
notificationList.add(hostAddress)
startForegroundNotification()
}
startForegroundNotification()
postEvent(EventBus.WEB_SERVICE, hostAddress)
}
}
@ -184,6 +183,7 @@ class WebService : BaseService() {
*/
override fun startForegroundNotification() {
val builder = NotificationCompat.Builder(this, AppConst.channelIdWeb)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_web_service_noti)
.setOngoing(true)
.setContentTitle(getString(R.string.web_service))
@ -196,7 +196,6 @@ class WebService : BaseService() {
getString(R.string.cancel),
servicePendingIntent<WebService>(IntentAction.stop)
)
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
val notification = builder.build()
startForeground(NotificationId.WebService, notification)
}

View File

@ -11,6 +11,7 @@ import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.help.book.getBookSource
import io.legado.app.help.book.removeType
import io.legado.app.model.AudioPlay
import io.legado.app.model.AudioPlay.durChapter

View File

@ -20,7 +20,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import java.util.*
import java.util.Collections
class RemoteBookViewModel(application: Application) : BaseViewModel(application) {
var sortKey = RemoteBookSort.Default
@ -78,6 +78,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
}
return@sortedWith compare
}
else -> list.sortedWith { o1, o2 ->
val compare = -compareValues(o1.isDir, o2.isDir)
if (compare == 0) {
@ -132,10 +133,8 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
val downloadBookUri = bookWebDav.downloadRemoteBook(remoteBook)
LocalBook.importFiles(downloadBookUri).forEach { book ->
book.origin = BookType.webDavTag + CustomUrl(remoteBook.path)
.putAttribute(
"serverID",
bookWebDav.serverID
).toString()
.putAttribute("serverID", bookWebDav.serverID)
.toString()
book.save()
}
remoteBook.isOnBookShelf = true
@ -152,7 +151,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
}
fun updateCallBackFlow(filterKey: String?) {
dataCallback?.screen(filterKey)
dataCallback?.screen(filterKey)
}
interface DataCallback {

View File

@ -1304,7 +1304,7 @@ class ReadBookActivity : BaseReadBookActivity(),
when (dialogId) {
TEXT_COLOR -> {
setCurTextColor(color)
postEvent(EventBus.UP_CONFIG, arrayListOf(2, 9, 11))
postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
}
BG_COLOR -> {
@ -1500,9 +1500,9 @@ class ReadBookActivity : BaseReadBookActivity(),
5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false)
6 -> readView.upContent(resetPageOffset = false)
8 -> ChapterProvider.upStyle()
9 -> binding.readView.invalidateTextPage()
9 -> readView.invalidateTextPage()
10 -> ChapterProvider.upLayout()
11 -> binding.readView.submitRenderTask()
11 -> readView.submitRenderTask()
}
}
}
@ -1543,10 +1543,10 @@ class ReadBookActivity : BaseReadBookActivity(),
viewModel.searchResultList = it
}
observeEvent<Boolean>(EventBus.UPDATE_READ_ACTION_BAR) {
binding.readMenu.reset()
readMenu.reset()
}
observeEvent<Boolean>(EventBus.UP_SEEK_BAR) {
binding.readMenu.upSeekBar()
readMenu.upSeekBar()
}
}

View File

@ -20,6 +20,7 @@ import io.legado.app.help.AppWebDav
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isLocal
import io.legado.app.help.book.isLocalModified
import io.legado.app.help.book.removeType
import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine

View File

@ -10,6 +10,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.book.BookContent
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.getBookSource
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine

View File

@ -6,10 +6,8 @@ import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.os.Build
import android.text.StaticLayout
import android.text.style.LineHeightSpan
import android.util.AttributeSet
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatTextView
@ -29,13 +27,6 @@ class BatteryView @JvmOverloads constructor(
private val outFrame = Rect()
private val polar = Rect()
private val canvasRecorder = CanvasRecorderFactory.create()
private val batterySpan = LineHeightSpan { _, _, _, _, _, fm ->
fm.top = -22
fm.ascent = -28
fm.descent = 7
fm.bottom = 1
fm.leading = 0
}
var isBattery = false
set(value) {
field = value
@ -48,6 +39,9 @@ class BatteryView @JvmOverloads constructor(
init {
setPadding(4.dpToPx(), 3.dpToPx(), 6.dpToPx(), 3.dpToPx())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
isFallbackLineSpacing = false
}
batteryPaint.strokeWidth = 1f.dpToPx()
batteryPaint.isAntiAlias = true
batteryPaint.color = paint.color
@ -69,9 +63,9 @@ class BatteryView @JvmOverloads constructor(
fun setBattery(battery: Int, text: String? = null) {
this.battery = battery
if (text.isNullOrEmpty()) {
setText(getBatteryText(battery.toString()))
setText(battery.toString())
} else {
setText(getBatteryText("$text $battery"))
setText("$text $battery")
}
}
@ -84,17 +78,16 @@ class BatteryView @JvmOverloads constructor(
if (AppConfig.optimizeRender) {
canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
super.onDraw(this)
if (!isBattery) return@recordIfNeededThenDraw
drawBattery(this)
}
} else {
super.onDraw(canvas)
if (!isBattery) return
drawBattery(canvas)
}
}
private fun drawBattery(canvas: Canvas) {
if (!isBattery) return
layout.getLineBounds(0, outFrame)
val batteryStart = layout
.getPrimaryHorizontal(text.length - battery.toString().length)
@ -120,22 +113,10 @@ class BatteryView @JvmOverloads constructor(
canvas.drawRect(polar, batteryPaint)
}
private fun getBatteryText(text: CharSequence?): SpannableStringBuilder? {
if (text == null) {
return null
}
return SpannableStringBuilder(text).apply {
setSpan(batterySpan, 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
@Suppress("UNNECESSARY_SAFE_CALL")
override fun invalidate() {
super.invalidate()
kotlin.runCatching {
canvasRecorder.invalidate()
}
canvasRecorder?.invalidate()
}
}

View File

@ -194,10 +194,10 @@ object NetworkUtils {
enumeration = NetworkInterface.getNetworkInterfaces()
} catch (e: SocketException) {
e.printOnDebug()
return mutableListOf()
return emptyList()
}
var fallbackAddress: MutableList<InetAddress> = mutableListOf()
val addressList = mutableListOf<InetAddress>()
while (enumeration.hasMoreElements()) {
val nif = enumeration.nextElement()
@ -205,13 +205,11 @@ object NetworkUtils {
while (addresses.hasMoreElements()) {
val address = addresses.nextElement()
if (!address.isLoopbackAddress && isIPv4Address(address.hostAddress)) {
if (nif.name?.startsWith("wlan") == true) {
fallbackAddress.add(address)
}
addressList.add(address)
}
}
}
return fallbackAddress
return addressList
}
/**