mirror of
https://github.com/gedoor/legado.git
synced 2024-07-04 23:36:56 +08:00
Compare commits
15 Commits
e327ffa179
...
4e0439396b
Author | SHA1 | Date | |
---|---|---|---|
|
4e0439396b | ||
|
4c6ec35d95 | ||
|
b30680c02e | ||
|
a3685c4358 | ||
|
a2cc621917 | ||
|
27571f5b06 | ||
|
0d63180dd0 | ||
|
9e5cefc47c | ||
|
5336adc035 | ||
|
72ad27ea84 | ||
|
5eaf2f034f | ||
|
2aed021e08 | ||
|
06d486c96c | ||
|
a35b7737aa | ||
|
9130452f06 |
2
.github/workflows/web.yml
vendored
2
.github/workflows/web.yml
vendored
|
@ -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,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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -71,6 +71,8 @@ open class WebDav(
|
||||||
<resourcetype />
|
<resourcetype />
|
||||||
</prop>
|
</prop>
|
||||||
</propfind>"""
|
</propfind>"""
|
||||||
|
|
||||||
|
private const val DEFAULT_CONTENT_TYPE = "application/octet-stream"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -304,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("文件不存在")
|
||||||
|
@ -336,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) {
|
||||||
|
@ -356,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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"sass": "^1.62.1",
|
"sass": "^1.62.1",
|
||||||
"unplugin-auto-import": "^0.17.5",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
"unplugin-icons": "^0.18.5",
|
"unplugin-icons": "^0.18.5",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^0.27.0",
|
||||||
"vite": "^5.1.3"
|
"vite": "^5.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user