Compare commits

...

16 Commits

Author SHA1 Message Date
dependabot[bot]
d7391ff4bd
Merge e95db8ea1d into 4c6ec35d95 2024-05-26 21:45:38 +08:00
Horis
4c6ec35d95 优化 2024-05-25 12:18:51 +08:00
Horis
b30680c02e
Merge pull request #3946 from mgz0227/master
替换被封域名
2024-05-24 11:09:29 +08:00
miaogongzi
a3685c4358 替换被封域名 2024-05-24 02:33:20 +08:00
Horis
a2cc621917 优化 2024-05-22 10:34:26 +08:00
Horis
27571f5b06 优化 2024-05-19 17:38:16 +08:00
Horis
0d63180dd0 优化 2024-05-18 00:04:54 +08:00
Horis
9e5cefc47c 优化 2024-05-17 21:57:15 +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
30 changed files with 187 additions and 178 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@ -1,7 +1,7 @@
[ [
{ {
"uploadUrl": "https://sy.mgz6.cc/shuyuan,{\"method\":\"POST\",\"body\": {\"file\": \"fileRequest\"},\"type\": \"multipart/form-data\"}", "uploadUrl": "https://sy.mgz6.com/shuyuan,{\"method\":\"POST\",\"body\": {\"file\": \"fileRequest\"},\"type\": \"multipart/form-data\"}",
"downloadUrlRule": "$.data@js:if (result == '') \n '' \n else \n 'https://shuyuan.mgz6.cc/shuyuan/' + result", "downloadUrlRule": "$.data@js:if (result == '') \n '' \n else \n 'https://shuyuan.mgz6.com/shuyuan/' + result",
"summary": "喵公子网盘①(有效期7天)", "summary": "喵公子网盘①(有效期7天)",
"compress": false "compress": false
}, },

View File

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

View File

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

View File

@ -5,17 +5,26 @@ package io.legado.app.help.book
import android.net.Uri import android.net.Uri
import com.script.SimpleBindings import com.script.SimpleBindings
import com.script.rhino.RhinoScriptEngine 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.appDb
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig 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 splitties.init.appCtx
import java.io.File import java.io.File
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.set
val Book.isAudio: Boolean val Book.isAudio: Boolean
@ -223,6 +232,14 @@ fun Book.sync(oldBook: Book) {
canUpdate = curBook.canUpdate 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 { fun Book.isSameNameAuthor(other: Any?): Boolean {
if (other is BaseBook) { if (other is BaseBook) {
return name == other.name && author == other.author return name == other.name && author == other.author

View File

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

View File

@ -93,7 +93,7 @@ object ReadBook : CoroutineScope by MainScope() {
readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0 readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl) chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
contentProcessor = ContentProcessor.get(book) contentProcessor = ContentProcessor.get(book)
durChapterIndex = min(book.durChapterIndex, chapterSize - 1).coerceAtLeast(0) durChapterIndex = book.durChapterIndex
durChapterPos = book.durChapterPos durChapterPos = book.durChapterPos
isLocalBook = book.isLocal isLocalBook = book.isLocal
clearTextChapter() clearTextChapter()

View File

@ -179,6 +179,14 @@ class EpubFile(var book: Book) {
elements.select("img[src=\"cover.jpeg\"]").forEachIndexed { i, it -> elements.select("img[src=\"cover.jpeg\"]").forEachIndexed { i, it ->
if (i > 0) it.remove() if (i > 0) it.remove()
} }
elements.select("img").forEach {
if (it.attributesSize() <= 1) {
return@forEach
}
val src = it.attr("src")
it.clearAttributes()
it.attr("src", src)
}
val tag = Book.rubyTag val tag = Book.rubyTag
if (book.getDelTag(tag)) { if (book.getDelTag(tag)) {
elements.select("rp, rt").remove() elements.select("rp, rt").remove()

View File

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

View File

@ -96,12 +96,11 @@ class WebService : BaseService() {
if (addressList.any()) { if (addressList.any()) {
notificationList.addAll(addressList.map { address -> getString(R.string.http_ip, address.hostAddress, getPort()) }) notificationList.addAll(addressList.map { address -> getString(R.string.http_ip, address.hostAddress, getPort()) })
hostAddress = notificationList.first() hostAddress = notificationList.first()
startForegroundNotification()
} else { } else {
hostAddress = getString(R.string.network_connection_unavailable) hostAddress = getString(R.string.network_connection_unavailable)
notificationList.add(hostAddress) notificationList.add(hostAddress)
startForegroundNotification()
} }
startForegroundNotification()
postEvent(EventBus.WEB_SERVICE, hostAddress) postEvent(EventBus.WEB_SERVICE, hostAddress)
} }
} }
@ -184,6 +183,7 @@ class WebService : BaseService() {
*/ */
override fun startForegroundNotification() { override fun startForegroundNotification() {
val builder = NotificationCompat.Builder(this, AppConst.channelIdWeb) val builder = NotificationCompat.Builder(this, AppConst.channelIdWeb)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_web_service_noti) .setSmallIcon(R.drawable.ic_web_service_noti)
.setOngoing(true) .setOngoing(true)
.setContentTitle(getString(R.string.web_service)) .setContentTitle(getString(R.string.web_service))
@ -196,7 +196,6 @@ class WebService : BaseService() {
getString(R.string.cancel), getString(R.string.cancel),
servicePendingIntent<WebService>(IntentAction.stop) servicePendingIntent<WebService>(IntentAction.stop)
) )
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
val notification = builder.build() val notification = builder.build()
startForeground(NotificationId.WebService, notification) 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.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource 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.help.book.removeType
import io.legado.app.model.AudioPlay import io.legado.app.model.AudioPlay
import io.legado.app.model.AudioPlay.durChapter 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.callbackFlow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import java.util.* import java.util.Collections
class RemoteBookViewModel(application: Application) : BaseViewModel(application) { class RemoteBookViewModel(application: Application) : BaseViewModel(application) {
var sortKey = RemoteBookSort.Default var sortKey = RemoteBookSort.Default
@ -78,6 +78,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
} }
return@sortedWith compare return@sortedWith compare
} }
else -> list.sortedWith { o1, o2 -> else -> list.sortedWith { o1, o2 ->
val compare = -compareValues(o1.isDir, o2.isDir) val compare = -compareValues(o1.isDir, o2.isDir)
if (compare == 0) { if (compare == 0) {
@ -132,10 +133,8 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
val downloadBookUri = bookWebDav.downloadRemoteBook(remoteBook) val downloadBookUri = bookWebDav.downloadRemoteBook(remoteBook)
LocalBook.importFiles(downloadBookUri).forEach { book -> LocalBook.importFiles(downloadBookUri).forEach { book ->
book.origin = BookType.webDavTag + CustomUrl(remoteBook.path) book.origin = BookType.webDavTag + CustomUrl(remoteBook.path)
.putAttribute( .putAttribute("serverID", bookWebDav.serverID)
"serverID", .toString()
bookWebDav.serverID
).toString()
book.save() book.save()
} }
remoteBook.isOnBookShelf = true remoteBook.isOnBookShelf = true
@ -152,7 +151,7 @@ class RemoteBookViewModel(application: Application) : BaseViewModel(application)
} }
fun updateCallBackFlow(filterKey: String?) { fun updateCallBackFlow(filterKey: String?) {
dataCallback?.screen(filterKey) dataCallback?.screen(filterKey)
} }
interface DataCallback { interface DataCallback {

View File

@ -303,9 +303,7 @@ class ReadBookActivity : BaseReadBookActivity(),
if (bookChanged) { if (bookChanged) {
bookChanged = false bookChanged = false
ReadBook.callBack = this ReadBook.callBack = this
viewModel.initData(intent) { viewModel.initData(intent)
upMenu()
}
} else { } else {
//web端阅读时app处于阅读界面本地记录会覆盖web保存的进度在此处恢复 //web端阅读时app处于阅读界面本地记录会覆盖web保存的进度在此处恢复
ReadBook.webBookProgress?.let { ReadBook.webBookProgress?.let {
@ -1304,7 +1302,7 @@ class ReadBookActivity : BaseReadBookActivity(),
when (dialogId) { when (dialogId) {
TEXT_COLOR -> { TEXT_COLOR -> {
setCurTextColor(color) setCurTextColor(color)
postEvent(EventBus.UP_CONFIG, arrayListOf(2, 9, 11)) postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
} }
BG_COLOR -> { BG_COLOR -> {
@ -1500,9 +1498,9 @@ class ReadBookActivity : BaseReadBookActivity(),
5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false) 5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false)
6 -> readView.upContent(resetPageOffset = false) 6 -> readView.upContent(resetPageOffset = false)
8 -> ChapterProvider.upStyle() 8 -> ChapterProvider.upStyle()
9 -> binding.readView.invalidateTextPage() 9 -> readView.invalidateTextPage()
10 -> ChapterProvider.upLayout() 10 -> ChapterProvider.upLayout()
11 -> binding.readView.submitRenderTask() 11 -> readView.submitRenderTask()
} }
} }
} }
@ -1543,10 +1541,10 @@ class ReadBookActivity : BaseReadBookActivity(),
viewModel.searchResultList = it viewModel.searchResultList = it
} }
observeEvent<Boolean>(EventBus.UPDATE_READ_ACTION_BAR) { observeEvent<Boolean>(EventBus.UPDATE_READ_ACTION_BAR) {
binding.readMenu.reset() readMenu.reset()
} }
observeEvent<Boolean>(EventBus.UP_SEEK_BAR) { 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.BookHelp
import io.legado.app.help.book.ContentProcessor import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isLocal 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.book.removeType
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
@ -115,6 +116,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
ReadBook.loadOrUpContent() ReadBook.loadOrUpContent()
checkLocalBookFileExist(book) checkLocalBookFileExist(book)
} else { } else {
if (ReadBook.durChapterIndex > ReadBook.chapterSize - 1) {
ReadBook.durChapterIndex = ReadBook.chapterSize - 1
}
ReadBook.loadContent(resetPageOffset = false) ReadBook.loadContent(resetPageOffset = false)
checkLocalBookFileExist(book) checkLocalBookFileExist(book)
} }

View File

@ -159,6 +159,7 @@ class MoreConfigDialog : DialogFragment() {
} }
PreferKey.optimizeRender -> { PreferKey.optimizeRender -> {
ChapterProvider.upStyle()
ReadBook.callBack?.upPageAnim(true) ReadBook.callBack?.upPageAnim(true)
ReadBook.loadContent(false) ReadBook.loadContent(false)
} }

View File

@ -200,7 +200,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),
override fun selectFont(path: String) { override fun selectFont(path: String) {
if (path != ReadBookConfig.textFont) { if (path != ReadBookConfig.textFont) {
ReadBookConfig.textFont = path ReadBookConfig.textFont = path
postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) postEvent(EventBus.UP_CONFIG, arrayListOf(2, 5))
} }
} }

View File

@ -278,17 +278,18 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
if (textPos.compare(selectEnd) <= 0) { if (textPos.compare(selectEnd) <= 0) {
selectStart.upData(pos = textPos) selectStart.upData(pos = textPos)
upSelectedStart( upSelectedStart(
if (textPos.isTouch) textColumn.start else textColumn.end, if (textPos.columnIndex < textLine.columns.lastIndex) textColumn.start else textColumn.end,
textLine.lineBottom + relativeOffset, textLine.lineBottom + relativeOffset,
textLine.lineTop + relativeOffset textLine.lineTop + relativeOffset
) )
} else { } else {
reverseStartCursor = true reverseStartCursor = true
reverseEndCursor = false reverseEndCursor = false
selectEnd.columnIndex++
selectStartMoveIndex(selectEnd) selectStartMoveIndex(selectEnd)
selectEnd.upData(textPos) selectEnd.upData(textPos)
upSelectedEnd( upSelectedEnd(
if (selectEnd.isTouch || selectEnd.isLast) textColumn.end else textColumn.start, if (textPos.columnIndex > -1) textColumn.end else textColumn.start,
textLine.lineBottom + relativeOffset textLine.lineBottom + relativeOffset
) )
} }
@ -307,16 +308,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
if (textPos.compare(selectStart) >= 0) { if (textPos.compare(selectStart) >= 0) {
selectEnd.upData(textPos) selectEnd.upData(textPos)
upSelectedEnd( upSelectedEnd(
if (selectEnd.isTouch || selectEnd.isLast) textColumn.end else textColumn.start, if (textPos.columnIndex > -1) textColumn.end else textColumn.start,
textLine.lineBottom + relativeOffset textLine.lineBottom + relativeOffset
) )
} else { } else {
reverseEndCursor = true reverseEndCursor = true
reverseStartCursor = false reverseStartCursor = false
selectStart.columnIndex--
selectEndMoveIndex(selectStart) selectEndMoveIndex(selectStart)
selectStart.upData(textPos) selectStart.upData(textPos)
upSelectedStart( upSelectedStart(
if (textPos.isTouch) textColumn.start else textColumn.end, if (textPos.columnIndex < textLine.columns.lastIndex) textColumn.start else textColumn.end,
textLine.lineBottom + relativeOffset, textLine.lineBottom + relativeOffset,
textLine.lineTop + relativeOffset textLine.lineTop + relativeOffset
) )
@ -418,11 +420,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
} }
val isLast = columns.first().start < x val isLast = columns.first().start < x
val charIndex = if (isLast) columns.lastIndex else 0 val charIndex = if (isLast) columns.lastIndex + 1 else -1
val textColumn = if (isLast) columns.last() else columns.first() val textColumn = if (isLast) columns.last() else columns.first()
touched.invoke( touched.invoke(
relativeOffset, relativeOffset,
TextPos(relativePos, lineIndex, charIndex, false, isLast), TextPos(relativePos, lineIndex, charIndex),
textPage, textLine, textColumn textPage, textLine, textColumn
) )
return return
@ -489,18 +491,14 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
relativePagePos: Int, relativePagePos: Int,
lineIndex: Int, lineIndex: Int,
charIndex: Int, charIndex: Int,
isTouch: Boolean,
isLast: Boolean = false
) { ) {
selectStart.relativePagePos = relativePagePos selectStart.relativePagePos = relativePagePos
selectStart.lineIndex = lineIndex selectStart.lineIndex = lineIndex
selectStart.columnIndex = charIndex selectStart.columnIndex = charIndex
selectStart.isTouch = isTouch
selectStart.isLast = isLast
val textLine = relativePage(relativePagePos).getLine(lineIndex) val textLine = relativePage(relativePagePos).getLine(lineIndex)
val textColumn = textLine.getColumn(charIndex) val textColumn = textLine.getColumn(charIndex)
upSelectedStart( upSelectedStart(
textColumn.start, if (charIndex < textLine.columns.lastIndex) textColumn.start else textColumn.end,
textLine.lineBottom + relativeOffset(relativePagePos), textLine.lineBottom + relativeOffset(relativePagePos),
textLine.lineTop + relativeOffset(relativePagePos) textLine.lineTop + relativeOffset(relativePagePos)
) )
@ -508,7 +506,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
fun selectStartMoveIndex(textPos: TextPos) = textPos.run { fun selectStartMoveIndex(textPos: TextPos) = textPos.run {
selectStartMoveIndex(relativePagePos, lineIndex, columnIndex, isTouch, isLast) selectStartMoveIndex(relativePagePos, lineIndex, columnIndex)
} }
/** /**
@ -518,22 +516,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
relativePage: Int, relativePage: Int,
lineIndex: Int, lineIndex: Int,
charIndex: Int, charIndex: Int,
isTouch: Boolean,
isLast: Boolean = false
) { ) {
selectEnd.relativePagePos = relativePage selectEnd.relativePagePos = relativePage
selectEnd.lineIndex = lineIndex selectEnd.lineIndex = lineIndex
selectEnd.columnIndex = charIndex selectEnd.columnIndex = charIndex
selectEnd.isTouch = isTouch
selectEnd.isLast = isLast
val textLine = relativePage(relativePage).getLine(lineIndex) val textLine = relativePage(relativePage).getLine(lineIndex)
val textColumn = textLine.getColumn(charIndex) val textColumn = textLine.getColumn(charIndex)
upSelectedEnd(textColumn.end, textLine.lineBottom + relativeOffset(relativePage)) upSelectedEnd(
if (charIndex > -1) textColumn.end else textColumn.start,
textLine.lineBottom + relativeOffset(relativePage)
)
upSelectChars() upSelectChars()
} }
fun selectEndMoveIndex(textPos: TextPos) = textPos.run { fun selectEndMoveIndex(textPos: TextPos) = textPos.run {
selectEndMoveIndex(relativePagePos, lineIndex, columnIndex, isTouch, isLast) selectEndMoveIndex(relativePagePos, lineIndex, columnIndex)
} }
private fun upSelectChars() { private fun upSelectChars() {
@ -553,8 +550,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val compareStart = textPos.compare(selectStart) val compareStart = textPos.compare(selectStart)
val compareEnd = textPos.compare(selectEnd) val compareEnd = textPos.compare(selectEnd)
column.selected = when { column.selected = when {
compareStart == 0 -> selectStart.isTouch compareStart == 0 -> true
compareEnd == 0 -> selectEnd.isTouch || selectEnd.isLast compareEnd == 0 -> true
compareStart > 0 && compareEnd < 0 -> true compareStart > 0 && compareEnd < 0 -> true
else -> false else -> false
} }
@ -624,19 +621,19 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
if (column is TextColumn) { if (column is TextColumn) {
when { when {
compareStart == 0 -> { compareStart == 0 -> {
if (selectStart.isTouch) { if (textPos.columnIndex < textLine.columns.lastIndex) {
builder.append(column.charData) builder.append(column.charData)
} }
if ( if (
textLine.isParagraphEnd textLine.isParagraphEnd
&& charIndex == textLine.charSize - 1 && charIndex == textLine.columns.lastIndex
&& compareEnd != 0 && compareEnd != 0
) { ) {
builder.append("\n") builder.append("\n")
} }
} }
compareEnd == 0 -> if (selectEnd.isTouch || selectEnd.isLast) { compareEnd == 0 -> if (textPos.columnIndex > -1) {
builder.append(column.charData) builder.append(column.charData)
} }
@ -644,7 +641,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
builder.append(column.charData) builder.append(column.charData)
if ( if (
textLine.isParagraphEnd textLine.isParagraphEnd
&& charIndex == textLine.charSize - 1 && charIndex == textLine.columns.lastIndex
) { ) {
builder.append("\n") builder.append("\n")
} }

View File

@ -404,17 +404,9 @@ class PageView(context: Context) : FrameLayout(context) {
fun selectStartMoveIndex( fun selectStartMoveIndex(
relativePagePos: Int, relativePagePos: Int,
lineIndex: Int, lineIndex: Int,
charIndex: Int, charIndex: Int
isTouch: Boolean = true,
isLast: Boolean = false
) { ) {
binding.contentTextView.selectStartMoveIndex( binding.contentTextView.selectStartMoveIndex(relativePagePos, lineIndex, charIndex)
relativePagePos,
lineIndex,
charIndex,
isTouch,
isLast
)
} }
fun selectStartMoveIndex(textPos: TextPos) { fun selectStartMoveIndex(textPos: TextPos) {
@ -428,17 +420,9 @@ class PageView(context: Context) : FrameLayout(context) {
fun selectEndMoveIndex( fun selectEndMoveIndex(
relativePagePos: Int, relativePagePos: Int,
lineIndex: Int, lineIndex: Int,
charIndex: Int, charIndex: Int
isTouch: Boolean = true,
isLast: Boolean = false
) { ) {
binding.contentTextView.selectEndMoveIndex( binding.contentTextView.selectEndMoveIndex(relativePagePos, lineIndex, charIndex)
relativePagePos,
lineIndex,
charIndex,
isTouch,
isLast
)
} }
fun selectEndMoveIndex(textPos: TextPos) { fun selectEndMoveIndex(textPos: TextPos) {

View File

@ -200,6 +200,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
if (!pressDown) return true
val absX = abs(startX - event.x) val absX = abs(startX - event.x)
val absY = abs(startY - event.y) val absY = abs(startY - event.y)
if (!isMove) { if (!isMove) {
@ -443,9 +444,13 @@ class ReadView(context: Context, attrs: AttributeSet) :
curPage.selectText(x, y) { textPos -> curPage.selectText(x, y) { textPos ->
val compare = initialTextPos.compare(textPos) val compare = initialTextPos.compare(textPos)
when { when {
compare >= 0 -> { compare > 0 -> {
curPage.selectStartMoveIndex(textPos) curPage.selectStartMoveIndex(textPos)
curPage.selectEndMoveIndex(initialTextPos) curPage.selectEndMoveIndex(
initialTextPos.relativePagePos,
initialTextPos.lineIndex,
initialTextPos.columnIndex - 1
)
} }
else -> { else -> {

View File

@ -11,30 +11,22 @@ data class TextPos(
var relativePagePos: Int, var relativePagePos: Int,
var lineIndex: Int, var lineIndex: Int,
var columnIndex: Int, var columnIndex: Int,
var isTouch: Boolean = true,
var isLast: Boolean = false
) { ) {
fun upData( fun upData(
relativePos: Int, relativePos: Int,
lineIndex: Int, lineIndex: Int,
charIndex: Int, charIndex: Int,
isTouch: Boolean,
isLast: Boolean
) { ) {
this.relativePagePos = relativePos this.relativePagePos = relativePos
this.lineIndex = lineIndex this.lineIndex = lineIndex
this.columnIndex = charIndex this.columnIndex = charIndex
this.isTouch = isTouch
this.isLast = isLast
} }
fun upData(pos: TextPos) { fun upData(pos: TextPos) {
relativePagePos = pos.relativePagePos relativePagePos = pos.relativePagePos
lineIndex = pos.lineIndex lineIndex = pos.lineIndex
columnIndex = pos.columnIndex columnIndex = pos.columnIndex
isTouch = pos.isTouch
isLast = pos.isLast
} }
fun compare(pos: TextPos): Int { fun compare(pos: TextPos): Int {
@ -65,8 +57,6 @@ data class TextPos(
relativePagePos = 0 relativePagePos = 0
lineIndex = -1 lineIndex = -1
columnIndex = -1 columnIndex = -1
isTouch = true
isLast = false
} }
fun isSelected(): Boolean { fun isSelected(): Boolean {

View File

@ -881,7 +881,9 @@ object ChapterProvider {
tPaint.typeface = titleFont tPaint.typeface = titleFont
tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx() tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx()
tPaint.isAntiAlias = true tPaint.isAntiAlias = true
tPaint.isLinearText = true if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) {
tPaint.isLinearText = true
}
//正文 //正文
val cPaint = TextPaint() val cPaint = TextPaint()
cPaint.color = ReadBookConfig.textColor cPaint.color = ReadBookConfig.textColor
@ -889,7 +891,9 @@ object ChapterProvider {
cPaint.typeface = textFont cPaint.typeface = textFont
cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx() cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx()
cPaint.isAntiAlias = true cPaint.isAntiAlias = true
cPaint.isLinearText = true if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) {
cPaint.isLinearText = true
}
return Pair(tPaint, cPaint) return Pair(tPaint, cPaint)
} }

View File

@ -10,6 +10,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.book.BookContent import io.legado.app.help.book.BookContent
import io.legado.app.help.book.BookHelp 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.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine

View File

@ -14,6 +14,8 @@ import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
@ -50,6 +52,7 @@ import io.legado.app.utils.ACache
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
import io.legado.app.utils.flowWithLifecycleFirst
import io.legado.app.utils.hideSoftInput import io.legado.app.utils.hideSoftInput
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.launch import io.legado.app.utils.launch
@ -68,6 +71,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@ -98,7 +102,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
override var sortAscending = true override var sortAscending = true
private set private set
private var snackBar: Snackbar? = null private var snackBar: Snackbar? = null
private var isPaused = false
private val qrResult = registerForActivityResult(QrCodeResult()) { private val qrResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult it ?: return@registerForActivityResult
showDialogFragment(ImportBookSourceDialog(it)) showDialogFragment(ImportBookSourceDialog(it))
@ -125,6 +128,19 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
} }
} }
} }
private val groupMenuLifecycleOwner = object : LifecycleOwner {
private val registry = LifecycleRegistry(this)
override val lifecycle: Lifecycle get() = registry
fun onMenuOpened() {
registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
fun onMenuClosed() {
registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
initRecyclerView() initRecyclerView()
@ -372,12 +388,17 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private fun initLiveDataGroup() { private fun initLiveDataGroup() {
lifecycleScope.launch { lifecycleScope.launch {
appDb.bookSourceDao.flowGroups().conflate().collect { appDb.bookSourceDao.flowGroups()
groups.clear() .flowWithLifecycle(lifecycle)
groups.addAll(it) .flowWithLifecycleFirst(groupMenuLifecycleOwner.lifecycle)
upGroupMenu() .conflate()
delay(500) .distinctUntilChanged()
} .collect {
groups.clear()
groups.addAll(it)
upGroupMenu()
delay(500)
}
} }
} }
@ -400,6 +421,20 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
} }
} }
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
if (menu === groupMenu) {
groupMenuLifecycleOwner.onMenuOpened()
}
return super.onMenuOpened(featureId, menu)
}
override fun onPanelClosed(featureId: Int, menu: Menu) {
super.onPanelClosed(featureId, menu)
if (menu === groupMenu) {
groupMenuLifecycleOwner.onMenuClosed()
}
}
private fun initSelectActionBar() { private fun initSelectActionBar() {
binding.selectActionBar.setMainActionText(R.string.delete) binding.selectActionBar.setMainActionText(R.string.delete)
binding.selectActionBar.inflateMenu(R.menu.book_source_sel) binding.selectActionBar.inflateMenu(R.menu.book_source_sel)
@ -637,16 +672,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
} }
} }
override fun onPause() {
super.onPause()
isPaused = true
}
override fun onResume() {
super.onResume()
isPaused = false
}
override fun upCountView() { override fun upCountView() {
binding.selectActionBar binding.selectActionBar
.upCountView(adapter.selection.size, adapter.itemCount) .upCountView(adapter.selection.size, adapter.itemCount)

View File

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

View File

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

View File

@ -4,17 +4,14 @@ package io.legado.app.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.View
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.cardview.widget.CardView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
import io.legado.app.R import io.legado.app.databinding.ViewToastBinding
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
import splitties.views.inflate import splitties.systemservices.layoutInflater
private var toast: Toast? = null private var toast: Toast? = null
@ -31,14 +28,13 @@ fun Context.toastOnUi(message: CharSequence?, duration: Int = Toast.LENGTH_SHORT
kotlin.runCatching { kotlin.runCatching {
toast?.cancel() toast?.cancel()
toast = Toast(this) toast = Toast(this)
val toastView: View = inflate(R.layout.view_toast)
toast?.view = toastView
val cardView = toastView.findViewById<CardView>(R.id.cv_content)
cardView.setCardBackgroundColor(bottomBackground)
val isLight = ColorUtils.isColorLight(bottomBackground) val isLight = ColorUtils.isColorLight(bottomBackground)
val textView = toastView.findViewById<TextView>(R.id.tv_text) ViewToastBinding.inflate(layoutInflater).run {
textView.setTextColor(getPrimaryTextColor(isLight)) toast?.view = root
textView.text = message cvToast.setCardBackgroundColor(bottomBackground)
tvText.setTextColor(getPrimaryTextColor(isLight))
tvText.text = message
}
toast?.duration = duration toast?.duration = duration
toast?.show() toast?.show()
} }

View File

@ -6,7 +6,7 @@
android:orientation="vertical"> android:orientation="vertical">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/cv_content" android:id="@+id/cv_toast"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:cardCornerRadius="10dp" app:cardCornerRadius="10dp"