同步上游更新
This commit is contained in:
@@ -21,6 +21,7 @@ object EventBus {
|
|||||||
const val NOTIFY_MAIN = "notifyMain"
|
const val NOTIFY_MAIN = "notifyMain"
|
||||||
const val WEB_SERVICE = "webService"
|
const val WEB_SERVICE = "webService"
|
||||||
const val UP_DOWNLOAD = "upDownload"
|
const val UP_DOWNLOAD = "upDownload"
|
||||||
|
const val UP_DOWNLOAD_STATE = "upDownloadState"
|
||||||
const val SAVE_CONTENT = "saveContent"
|
const val SAVE_CONTENT = "saveContent"
|
||||||
const val CHECK_SOURCE = "checkSource"
|
const val CHECK_SOURCE = "checkSource"
|
||||||
const val CHECK_SOURCE_DONE = "checkSourceDone"
|
const val CHECK_SOURCE_DONE = "checkSourceDone"
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ data class HttpTTS(
|
|||||||
loginUrl = doc.readString("$.loginUrl"),
|
loginUrl = doc.readString("$.loginUrl"),
|
||||||
loginUi = if (loginUi is List<*>) GSON.toJson(loginUi) else loginUi?.toString(),
|
loginUi = if (loginUi is List<*>) GSON.toJson(loginUi) else loginUi?.toString(),
|
||||||
header = doc.readString("$.header"),
|
header = doc.readString("$.header"),
|
||||||
loginCheckJs = doc.readString("$.loginCheckJs")
|
loginCheckJs = doc.readString("$.loginCheckJs"),
|
||||||
|
lastUpdateTime = doc.readLong("$.lastUpdateTime") ?: System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class ReadRecordRepository(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// 2分钟内重新打开书本,视为同一次阅读
|
// 10秒内重新打开书本,视为同一次阅读
|
||||||
private const val CONTINUE_THRESHOLD = 120 * 1000L
|
private const val CONTINUE_THRESHOLD = 10 * 1000L
|
||||||
// 会话总时长小于10秒则不记录
|
// 会话总时长小于10秒则不记录
|
||||||
private const val MIN_READ_DURATION = 0 * 1000L
|
private const val MIN_READ_DURATION = 0 * 1000L
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package io.legado.app.help.glide.progress
|
package io.legado.app.help.glide.progress
|
||||||
|
|
||||||
typealias OnProgressListener = ((isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit)?
|
typealias OnProgressListener = (isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit
|
||||||
@@ -28,7 +28,7 @@ object ProgressManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addListener(url: String, listener: OnProgressListener) {
|
fun addListener(url: String, listener: OnProgressListener) {
|
||||||
if (url.isNotEmpty() && listener != null) {
|
if (url.isNotEmpty()) {
|
||||||
val url = getUrlNoOption(url)
|
val url = getUrlNoOption(url)
|
||||||
listenersMap[url] = listener
|
listenersMap[url] = listener
|
||||||
listener.invoke(false, 1, 0, 0)
|
listener.invoke(false, 1, 0, 0)
|
||||||
@@ -42,7 +42,7 @@ object ProgressManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProgressListener(url: String): OnProgressListener {
|
fun getProgressListener(url: String): OnProgressListener? {
|
||||||
return if (url.isEmpty() || listenersMap.isEmpty()) {
|
return if (url.isEmpty() || listenersMap.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
@@ -53,7 +53,7 @@ object ProgressManager {
|
|||||||
private fun getUrlNoOption(url: String): String {
|
private fun getUrlNoOption(url: String): String {
|
||||||
val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)
|
val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)
|
||||||
return if (urlMatcher.find()) {
|
return if (urlMatcher.find()) {
|
||||||
url.substring(0, urlMatcher.start())
|
url.take(urlMatcher.start())
|
||||||
} else {
|
} else {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,25 +11,39 @@ import io.legado.app.data.entities.BookSource
|
|||||||
import io.legado.app.exception.ConcurrentException
|
import io.legado.app.exception.ConcurrentException
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.coroutine.CompositeCoroutine
|
import io.legado.app.help.coroutine.CompositeCoroutine
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.model.webBook.WebBook
|
import io.legado.app.model.webBook.WebBook
|
||||||
import io.legado.app.service.CacheBookService
|
import io.legado.app.service.CacheBookService
|
||||||
|
import io.legado.app.utils.onEachParallel
|
||||||
import io.legado.app.utils.postEvent
|
import io.legado.app.utils.postEvent
|
||||||
import io.legado.app.utils.startService
|
import io.legado.app.utils.startService
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
object CacheBook {
|
object CacheBook {
|
||||||
|
|
||||||
val cacheBookMap = ConcurrentHashMap<String, CacheBookModel>()
|
val cacheBookMap = ConcurrentHashMap<String, CacheBookModel>()
|
||||||
|
private val workingState = MutableStateFlow(true)
|
||||||
|
private val mutex = Mutex()
|
||||||
val totalCount: Int
|
val totalCount: Int
|
||||||
get() {
|
get() {
|
||||||
var total = 0
|
var total = 0
|
||||||
@@ -117,6 +131,39 @@ object CacheBook {
|
|||||||
errorDownloadMap.clear()
|
errorDownloadMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setWorkingState(value: Boolean) {
|
||||||
|
workingState.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun startProcessJob(context: CoroutineContext) = mutex.withLock {
|
||||||
|
setWorkingState(true)
|
||||||
|
flow {
|
||||||
|
while (currentCoroutineContext().isActive && cacheBookMap.isNotEmpty()) {
|
||||||
|
var emitted = false
|
||||||
|
|
||||||
|
cacheBookMap.forEach { (_, model) ->
|
||||||
|
if (!model.isLoading()) {
|
||||||
|
emit(model)
|
||||||
|
emitted = true
|
||||||
|
}
|
||||||
|
workingState.first { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emitted) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onStart {
|
||||||
|
postEvent(EventBus.UP_DOWNLOAD_STATE, "")
|
||||||
|
}.onEachParallel(AppConfig.threadCount) {
|
||||||
|
coroutineScope {
|
||||||
|
it.download(this, context)
|
||||||
|
}
|
||||||
|
}.onCompletion {
|
||||||
|
postEvent(EventBus.UP_DOWNLOAD_STATE, "")
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
|
|
||||||
val downloadSummary: String
|
val downloadSummary: String
|
||||||
get() {
|
get() {
|
||||||
return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorDownloadMap.count()}|成功:${successDownloadSet.size}"
|
return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorDownloadMap.count()}|成功:${successDownloadSet.size}"
|
||||||
@@ -124,11 +171,12 @@ object CacheBook {
|
|||||||
|
|
||||||
val isRun: Boolean
|
val isRun: Boolean
|
||||||
get() {
|
get() {
|
||||||
var isRun = false
|
|
||||||
cacheBookMap.forEach {
|
cacheBookMap.forEach {
|
||||||
isRun = isRun || it.value.isRun()
|
if (it.value.isRun()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return isRun
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val waitCount: Int
|
private val waitCount: Int
|
||||||
@@ -159,6 +207,7 @@ object CacheBook {
|
|||||||
private val tasks = CompositeCoroutine()
|
private val tasks = CompositeCoroutine()
|
||||||
private var isStopped = false
|
private var isStopped = false
|
||||||
private var waitingRetry = false
|
private var waitingRetry = false
|
||||||
|
private var isLoading = false
|
||||||
|
|
||||||
val waitCount get() = waitDownloadSet.size
|
val waitCount get() = waitDownloadSet.size
|
||||||
val onDownloadCount get() = onDownloadSet.size
|
val onDownloadCount get() = onDownloadSet.size
|
||||||
@@ -169,7 +218,7 @@ object CacheBook {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun isRun(): Boolean {
|
fun isRun(): Boolean {
|
||||||
return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty()
|
return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty() || isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -177,11 +226,22 @@ object CacheBook {
|
|||||||
return isStopped || (!isRun() && !waitingRetry)
|
return isStopped || (!isRun() && !waitingRetry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun isLoading(): Boolean {
|
||||||
|
return isLoading
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun setLoading() {
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun stop() {
|
fun stop() {
|
||||||
waitDownloadSet.clear()
|
waitDownloadSet.clear()
|
||||||
tasks.clear()
|
tasks.clear()
|
||||||
isStopped = true
|
isStopped = true
|
||||||
|
isLoading = false
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +254,7 @@ object CacheBook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cacheBookMap[book.bookUrl] = this
|
cacheBookMap[book.bookUrl] = this
|
||||||
|
isLoading = false
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,10 +318,9 @@ object CacheBook {
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun download(scope: CoroutineScope, context: CoroutineContext) {
|
fun download(scope: CoroutineScope, context: CoroutineContext) {
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
|
||||||
val chapterIndex = waitDownloadSet.firstOrNull()
|
val chapterIndex = waitDownloadSet.firstOrNull()
|
||||||
if (chapterIndex == null) {
|
if (chapterIndex == null) {
|
||||||
if (onDownloadSet.isEmpty()) {
|
if (!isLoading && onDownloadSet.isEmpty()) {
|
||||||
cacheBookMap.remove(book.bookUrl)
|
cacheBookMap.remove(book.bookUrl)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -331,7 +391,6 @@ object CacheBook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadAwait(chapter: BookChapter): String {
|
suspend fun downloadAwait(chapter: BookChapter): String {
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
onDownloadSet.add(chapter.index)
|
onDownloadSet.add(chapter.index)
|
||||||
waitDownloadSet.remove(chapter.index)
|
waitDownloadSet.remove(chapter.index)
|
||||||
@@ -365,7 +424,6 @@ object CacheBook {
|
|||||||
if (onDownloadSet.contains(chapter.index)) {
|
if (onDownloadSet.contains(chapter.index)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
|
||||||
onDownloadSet.add(chapter.index)
|
onDownloadSet.add(chapter.index)
|
||||||
waitDownloadSet.remove(chapter.index)
|
waitDownloadSet.remove(chapter.index)
|
||||||
WebBook.getContent(
|
WebBook.getContent(
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
readRecordRepository.saveOrMergeReadSession(sessionToSave)
|
readRecordRepository.saveReadSession(sessionToSave)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e)
|
AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
readRecordRepository.saveOrMergeReadSession(sessionToSave)
|
readRecordRepository.saveReadSession(sessionToSave)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e)
|
AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -121,12 +121,14 @@ class CacheBookService : BaseService() {
|
|||||||
val chapterCount = appDb.bookChapterDao.getChapterCount(bookUrl)
|
val chapterCount = appDb.bookChapterDao.getChapterCount(bookUrl)
|
||||||
|
|
||||||
if (chapterCount == 0) {
|
if (chapterCount == 0) {
|
||||||
|
cacheBook.setLoading()
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val name = book.name
|
val name = book.name
|
||||||
if (book.tocUrl.isEmpty()) {
|
if (book.tocUrl.isEmpty()) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
WebBook.getBookInfoAwait(cacheBook.bookSource, book)
|
WebBook.getBookInfoAwait(cacheBook.bookSource, book)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
removeDownload(bookUrl)
|
||||||
AppLog.put(
|
AppLog.put(
|
||||||
"《$name》目录为空且加载详情页失败\n${it.localizedMessage}",
|
"《$name》目录为空且加载详情页失败\n${it.localizedMessage}",
|
||||||
it,
|
it,
|
||||||
@@ -141,6 +143,7 @@ class CacheBookService : BaseService() {
|
|||||||
book.totalChapterNum = 0
|
book.totalChapterNum = 0
|
||||||
book.update()
|
book.update()
|
||||||
}
|
}
|
||||||
|
removeDownload(bookUrl)
|
||||||
AppLog.put(
|
AppLog.put(
|
||||||
"《$name》目录为空且加载目录失败\n${it.localizedMessage}",
|
"《$name》目录为空且加载目录失败\n${it.localizedMessage}",
|
||||||
it,
|
it,
|
||||||
@@ -189,23 +192,8 @@ class CacheBookService : BaseService() {
|
|||||||
private fun download() {
|
private fun download() {
|
||||||
downloadJob?.cancel()
|
downloadJob?.cancel()
|
||||||
downloadJob = lifecycleScope.launch(cachePool) {
|
downloadJob = lifecycleScope.launch(cachePool) {
|
||||||
while (isActive) {
|
CacheBook.startProcessJob(cachePool)
|
||||||
if (!CacheBook.isRun) {
|
stopSelf()
|
||||||
stopSelf()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
CacheBook.cacheBookMap.forEach {
|
|
||||||
val cacheBookModel = it.value
|
|
||||||
while (cacheBookModel.waitCount > 0) {
|
|
||||||
if (CacheBook.onDownloadCount < threadCount) {
|
|
||||||
cacheBookModel.download(this, cachePool)
|
|
||||||
} else {
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.utils.contains
|
import io.legado.app.utils.contains
|
||||||
import io.legado.app.utils.inputStream
|
import io.legado.app.utils.inputStream
|
||||||
|
import io.legado.app.utils.jsonPath
|
||||||
import io.legado.app.utils.readText
|
import io.legado.app.utils.readText
|
||||||
|
|
||||||
abstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) {
|
abstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) {
|
||||||
@@ -14,28 +15,33 @@ abstract class BaseAssociationViewModel(application: Application) : BaseViewMode
|
|||||||
val errorLive = MutableLiveData<String>()
|
val errorLive = MutableLiveData<String>()
|
||||||
|
|
||||||
fun importJson(uri: Uri) {
|
fun importJson(uri: Uri) {
|
||||||
when {
|
val map = uri.inputStream(context).getOrThrow().use {
|
||||||
uri.inputStream(context).getOrNull().contains("bookSourceUrl") ->
|
jsonPath.parse(it).read<Map<String, *>>("$[0]")
|
||||||
successLive.postValue(Pair("bookSource", uri.toString()))
|
} ?: uri.inputStream(context).getOrThrow().use {
|
||||||
else -> importJson(uri.readText(context))
|
jsonPath.parse(it).read("$")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun importJson(json: String) {
|
|
||||||
//暂时根据文件内容判断属于什么
|
|
||||||
when {
|
when {
|
||||||
json.contains("sourceUrl") ->
|
map.containsKey("bookSourceUrl") ->
|
||||||
successLive.postValue(Pair("rssSource", json))
|
successLive.postValue("bookSource" to uri.toString())
|
||||||
json.contains("pattern") ->
|
|
||||||
successLive.postValue(Pair("replaceRule", json))
|
map.containsKey("sourceUrl") ->
|
||||||
json.contains("themeName") ->
|
successLive.postValue("rssSource" to uri.toString())
|
||||||
successLive.postValue(Pair("theme", json))
|
|
||||||
json.contains("urlRule") && json.contains("showRule") ->
|
map.containsKey("pattern") ->
|
||||||
successLive.postValue(Pair("dictRule", json))
|
successLive.postValue("replaceRule" to uri.toString())
|
||||||
json.contains("name") && json.contains("rule") ->
|
|
||||||
successLive.postValue(Pair("txtRule", json))
|
map.containsKey("themeName") ->
|
||||||
json.contains("name") && json.contains("url") ->
|
successLive.postValue("theme" to uri.toString())
|
||||||
successLive.postValue(Pair("httpTts", json))
|
|
||||||
|
map.containsKey("showRule") ->
|
||||||
|
successLive.postValue("dictRule" to uri.toString())
|
||||||
|
|
||||||
|
map.containsKey("name") && map.containsKey("rule") ->
|
||||||
|
successLive.postValue("txtRule" to uri.toString())
|
||||||
|
|
||||||
|
map.containsKey("name") && map.containsKey("url") ->
|
||||||
|
successLive.postValue("httpTts" to uri.toString())
|
||||||
|
|
||||||
else -> errorLive.postValue("格式不对")
|
else -> errorLive.postValue("格式不对")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
@@ -19,6 +20,9 @@ import io.legado.app.utils.fromJsonObject
|
|||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.isJsonArray
|
import io.legado.app.utils.isJsonArray
|
||||||
import io.legado.app.utils.isJsonObject
|
import io.legado.app.utils.isJsonObject
|
||||||
|
import io.legado.app.utils.isUri
|
||||||
|
import io.legado.app.utils.readText
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class ImportDictRuleViewModel(app: Application) : BaseViewModel(app) {
|
class ImportDictRuleViewModel(app: Application) : BaseViewModel(app) {
|
||||||
|
|
||||||
@@ -88,6 +92,9 @@ class ImportDictRuleViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
text.isAbsUrl() -> {
|
text.isAbsUrl() -> {
|
||||||
importSourceUrl(text)
|
importSourceUrl(text)
|
||||||
}
|
}
|
||||||
|
text.isUri() -> {
|
||||||
|
importSourceAwait(text.toUri().readText(appCtx))
|
||||||
|
}
|
||||||
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
@@ -16,6 +17,9 @@ import io.legado.app.help.http.text
|
|||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.isJsonArray
|
import io.legado.app.utils.isJsonArray
|
||||||
import io.legado.app.utils.isJsonObject
|
import io.legado.app.utils.isJsonObject
|
||||||
|
import io.legado.app.utils.isUri
|
||||||
|
import io.legado.app.utils.readText
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) {
|
class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) {
|
||||||
|
|
||||||
@@ -85,6 +89,9 @@ class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
text.isAbsUrl() -> {
|
text.isAbsUrl() -> {
|
||||||
importSourceUrl(text)
|
importSourceUrl(text)
|
||||||
}
|
}
|
||||||
|
text.isUri() -> {
|
||||||
|
importSourceAwait(text.toUri().readText(appCtx))
|
||||||
|
}
|
||||||
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.constant.AppConst
|
import io.legado.app.constant.AppConst
|
||||||
@@ -17,7 +18,10 @@ import io.legado.app.help.http.text
|
|||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.isJsonArray
|
import io.legado.app.utils.isJsonArray
|
||||||
import io.legado.app.utils.isJsonObject
|
import io.legado.app.utils.isJsonObject
|
||||||
|
import io.legado.app.utils.isUri
|
||||||
|
import io.legado.app.utils.readText
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.utils.splitNotBlank
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
|
class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
|
||||||
var isAddGroup = false
|
var isAddGroup = false
|
||||||
@@ -102,6 +106,10 @@ class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
allRules.add(rule)
|
allRules.add(rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text.isUri() -> {
|
||||||
|
importAwait(text.toUri().readText(appCtx))
|
||||||
|
}
|
||||||
|
|
||||||
else -> throw NoStackTraceException("格式不对")
|
else -> throw NoStackTraceException("格式不对")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.jayway.jsonpath.JsonPath
|
import com.jayway.jsonpath.JsonPath
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
@@ -22,8 +23,11 @@ import io.legado.app.utils.fromJsonObject
|
|||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.isJsonArray
|
import io.legado.app.utils.isJsonArray
|
||||||
import io.legado.app.utils.isJsonObject
|
import io.legado.app.utils.isJsonObject
|
||||||
|
import io.legado.app.utils.isUri
|
||||||
import io.legado.app.utils.jsonPath
|
import io.legado.app.utils.jsonPath
|
||||||
|
import io.legado.app.utils.readText
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.utils.splitNotBlank
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
|
class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
|
||||||
var isAddGroup = false
|
var isAddGroup = false
|
||||||
@@ -101,42 +105,7 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
|
|
||||||
fun importSource(text: String) {
|
fun importSource(text: String) {
|
||||||
execute {
|
execute {
|
||||||
val mText = text.trim()
|
importSourceAwait(text)
|
||||||
when {
|
|
||||||
mText.isJsonObject() -> kotlin.runCatching {
|
|
||||||
val json = JsonPath.parse(mText)
|
|
||||||
val urls = json.read<List<String>>("$.sourceUrls")
|
|
||||||
if (!urls.isNullOrEmpty()) {
|
|
||||||
urls.forEach {
|
|
||||||
importSourceUrl(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
|
|
||||||
val source = it.firstOrNull() ?: return@let
|
|
||||||
if (source.sourceUrl.isEmpty()) {
|
|
||||||
throw NoStackTraceException("不是订阅源")
|
|
||||||
}
|
|
||||||
allSources.addAll(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mText.isJsonArray() -> {
|
|
||||||
GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
|
|
||||||
val source = it.firstOrNull() ?: return@let
|
|
||||||
if (source.sourceUrl.isEmpty()) {
|
|
||||||
throw NoStackTraceException("不是订阅源")
|
|
||||||
}
|
|
||||||
allSources.addAll(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mText.isAbsUrl() -> {
|
|
||||||
importSourceUrl(mText)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
|
||||||
}
|
|
||||||
}.onError {
|
}.onError {
|
||||||
errorLiveData.postValue("ImportError:${it.localizedMessage}")
|
errorLiveData.postValue("ImportError:${it.localizedMessage}")
|
||||||
AppLog.put("ImportError:${it.localizedMessage}", it)
|
AppLog.put("ImportError:${it.localizedMessage}", it)
|
||||||
@@ -145,6 +114,53 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun importSourceAwait(text: String) {
|
||||||
|
val mText = text.trim()
|
||||||
|
when {
|
||||||
|
mText.isJsonObject() -> kotlin.runCatching {
|
||||||
|
val json = JsonPath.parse(mText)
|
||||||
|
val urls = json.read<List<String>>("$.sourceUrls")
|
||||||
|
if (!urls.isNullOrEmpty()) {
|
||||||
|
urls.forEach {
|
||||||
|
importSourceUrl(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
|
||||||
|
val source = it.firstOrNull() ?: return@let
|
||||||
|
if (source.sourceUrl.isEmpty()) {
|
||||||
|
throw NoStackTraceException("不是订阅源")
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
allSources.addAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mText.isJsonArray() -> {
|
||||||
|
GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
|
||||||
|
val source = it.firstOrNull() ?: return@let
|
||||||
|
if (source.sourceUrl.isEmpty()) {
|
||||||
|
throw NoStackTraceException("不是订阅源")
|
||||||
|
}
|
||||||
|
allSources.addAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mText.isAbsUrl() -> {
|
||||||
|
importSourceUrl(mText)
|
||||||
|
}
|
||||||
|
|
||||||
|
mText.isUri() -> {
|
||||||
|
importSourceAwait(mText.toUri().readText(appCtx))
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun importSourceUrl(url: String) {
|
private suspend fun importSourceUrl(url: String) {
|
||||||
okHttpClient.newCallResponseBody {
|
okHttpClient.newCallResponseBody {
|
||||||
if (url.endsWith("#requestWithoutUA")) {
|
if (url.endsWith("#requestWithoutUA")) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
@@ -13,6 +14,7 @@ import io.legado.app.help.http.newCallResponseBody
|
|||||||
import io.legado.app.help.http.okHttpClient
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.help.http.text
|
import io.legado.app.help.http.text
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class ImportThemeViewModel(app: Application) : BaseViewModel(app) {
|
class ImportThemeViewModel(app: Application) : BaseViewModel(app) {
|
||||||
|
|
||||||
@@ -81,6 +83,9 @@ class ImportThemeViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
text.isAbsUrl() -> {
|
text.isAbsUrl() -> {
|
||||||
importSourceUrl(text)
|
importSourceUrl(text)
|
||||||
}
|
}
|
||||||
|
text.isUri() -> {
|
||||||
|
importSourceAwait(text.toUri().readText(appCtx))
|
||||||
|
}
|
||||||
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
@@ -19,6 +20,9 @@ import io.legado.app.utils.fromJsonObject
|
|||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.isJsonArray
|
import io.legado.app.utils.isJsonArray
|
||||||
import io.legado.app.utils.isJsonObject
|
import io.legado.app.utils.isJsonObject
|
||||||
|
import io.legado.app.utils.isUri
|
||||||
|
import io.legado.app.utils.readText
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class ImportTxtTocRuleViewModel(app: Application) : BaseViewModel(app) {
|
class ImportTxtTocRuleViewModel(app: Application) : BaseViewModel(app) {
|
||||||
|
|
||||||
@@ -89,6 +93,9 @@ class ImportTxtTocRuleViewModel(app: Application) : BaseViewModel(app) {
|
|||||||
text.isAbsUrl() -> {
|
text.isAbsUrl() -> {
|
||||||
importSourceUrl(text)
|
importSourceUrl(text)
|
||||||
}
|
}
|
||||||
|
text.isUri() -> {
|
||||||
|
importSourceAwait(text.toUri().readText(appCtx))
|
||||||
|
}
|
||||||
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,6 +299,9 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
|||||||
notifyItemChanged(it)
|
notifyItemChanged(it)
|
||||||
}
|
}
|
||||||
observeEvent<String>(EventBus.UP_DOWNLOAD) {
|
observeEvent<String>(EventBus.UP_DOWNLOAD) {
|
||||||
|
notifyItemChanged(it)
|
||||||
|
}
|
||||||
|
observeEvent<String>(EventBus.UP_DOWNLOAD_STATE) {
|
||||||
if (!CacheBook.isRun) {
|
if (!CacheBook.isRun) {
|
||||||
menu?.findItem(R.id.menu_download)?.let { item ->
|
menu?.findItem(R.id.menu_download)?.let { item ->
|
||||||
item.setIcon(R.drawable.ic_play)
|
item.setIcon(R.drawable.ic_play)
|
||||||
@@ -312,7 +315,6 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
|||||||
}
|
}
|
||||||
menu?.applyTint(this)
|
menu?.applyTint(this)
|
||||||
}
|
}
|
||||||
notifyItemChanged(it)
|
|
||||||
}
|
}
|
||||||
observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->
|
observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->
|
||||||
viewModel.cacheChapters[book.bookUrl]?.add(chapter.url)
|
viewModel.cacheChapters[book.bookUrl]?.add(chapter.url)
|
||||||
|
|||||||
@@ -48,20 +48,6 @@ abstract class BaseImportBookActivity<VM : ViewModel> :
|
|||||||
initSearchView()
|
initSearchView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置书籍保存位置
|
* 设置书籍保存位置
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ class ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, t
|
|||||||
private val webDavServerUi = listOf(
|
private val webDavServerUi = listOf(
|
||||||
RowUi("url"),
|
RowUi("url"),
|
||||||
RowUi("username"),
|
RowUi("username"),
|
||||||
RowUi("password", RowUi.Type.password)
|
RowUi("password", RowUi.Type.password),
|
||||||
|
RowUi("ipRange")
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
|||||||
@@ -118,20 +118,6 @@ class BookshelfManageActivity :
|
|||||||
upBookDataByGroupId()
|
upBookDataByGroupId()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun observeLiveBus() {
|
override fun observeLiveBus() {
|
||||||
viewModel.batchChangeSourceState.observe(this) {
|
viewModel.batchChangeSourceState.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ class InfoConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_info
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.btnPaddingSetting.setOnClickListener {
|
binding.btnPaddingSetting.setOnClickListener {
|
||||||
dismissAllowingStateLoss()
|
|
||||||
callBack?.showPaddingConfig()
|
callBack?.showPaddingConfig()
|
||||||
}
|
}
|
||||||
upTvTipColor()
|
upTvTipColor()
|
||||||
|
|||||||
@@ -101,12 +101,10 @@ class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_
|
|||||||
}
|
}
|
||||||
|
|
||||||
tvTextFont.setOnClickListener {
|
tvTextFont.setOnClickListener {
|
||||||
dismissAllowingStateLoss()
|
|
||||||
callBack?.showFont()
|
callBack?.showFont()
|
||||||
}
|
}
|
||||||
|
|
||||||
tvPadding.setOnClickListener {
|
tvPadding.setOnClickListener {
|
||||||
dismissAllowingStateLoss()
|
|
||||||
callBack?.showInfoConfig()
|
callBack?.showInfoConfig()
|
||||||
}
|
}
|
||||||
tvTip.setOnClickListener {
|
tvTip.setOnClickListener {
|
||||||
@@ -168,7 +166,6 @@ class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showBgTextConfig(index: Int): Boolean {
|
private fun showBgTextConfig(index: Int): Boolean {
|
||||||
dismissAllowingStateLoss()
|
|
||||||
changeBgTextConfig(index)
|
changeBgTextConfig(index)
|
||||||
callBack?.showBgTextConfig()
|
callBack?.showBgTextConfig()
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class SpeakEngineDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_recycl
|
|||||||
private val callBack: CallBack? get() = parentFragment as? CallBack
|
private val callBack: CallBack? get() = parentFragment as? CallBack
|
||||||
private val importDocResult = registerForActivityResult(HandleFileContract()) {
|
private val importDocResult = registerForActivityResult(HandleFileContract()) {
|
||||||
it.uri?.let { uri ->
|
it.uri?.let { uri ->
|
||||||
viewModel.importLocal(uri)
|
showDialogFragment(ImportHttpTtsDialog(uri.toString()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val exportDirResult = registerForActivityResult(HandleFileContract()) {
|
private val exportDirResult = registerForActivityResult(HandleFileContract()) {
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
package io.legado.app.ui.book.read.config
|
package io.legado.app.ui.book.read.config
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
|
||||||
import android.speech.tts.TextToSpeech
|
import android.speech.tts.TextToSpeech
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.HttpTTS
|
|
||||||
import io.legado.app.exception.NoStackTraceException
|
|
||||||
import io.legado.app.help.DefaultData
|
import io.legado.app.help.DefaultData
|
||||||
import io.legado.app.utils.isJsonArray
|
|
||||||
import io.legado.app.utils.isJsonObject
|
|
||||||
import io.legado.app.utils.readText
|
|
||||||
import io.legado.app.utils.toastOnUi
|
|
||||||
|
|
||||||
class SpeakEngineViewModel(application: Application) : BaseViewModel(application) {
|
class SpeakEngineViewModel(application: Application) : BaseViewModel(application) {
|
||||||
|
|
||||||
@@ -28,34 +20,4 @@ class SpeakEngineViewModel(application: Application) : BaseViewModel(application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importLocal(uri: Uri) {
|
|
||||||
execute {
|
|
||||||
import(uri.readText(context))
|
|
||||||
}.onSuccess {
|
|
||||||
context.toastOnUi("导入成功")
|
|
||||||
}.onError {
|
|
||||||
context.toastOnUi("导入失败\n${it.localizedMessage}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun import(text: String) {
|
|
||||||
when {
|
|
||||||
text.isJsonArray() -> {
|
|
||||||
HttpTTS.fromJsonArray(text).getOrThrow().let {
|
|
||||||
appDb.httpTTSDao.insert(*it.toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
text.isJsonObject() -> {
|
|
||||||
HttpTTS.fromJson(text).getOrThrow().let {
|
|
||||||
appDb.httpTTSDao.insert(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
throw NoStackTraceException("格式不对")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) {
|
|||||||
private val slopSquare get() = readView.pageSlopSquare2
|
private val slopSquare get() = readView.pageSlopSquare2
|
||||||
|
|
||||||
private var fadeProgress = 0f
|
private var fadeProgress = 0f
|
||||||
private val flipThreshold = 0.2f
|
private val flipThreshold = 0.1f
|
||||||
|
|
||||||
override fun setDirection(direction: PageDirection) {
|
override fun setDirection(direction: PageDirection) {
|
||||||
super.setDirection(direction)
|
super.setDirection(direction)
|
||||||
|
|||||||
@@ -95,20 +95,6 @@ class SearchContentActivity :
|
|||||||
return super.onCompatOptionsItemSelected(item)
|
return super.onCompatOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initSearchResultList(list: List<SearchResult>?, position: Int) {
|
private fun initSearchResultList(list: List<SearchResult>?, position: Int) {
|
||||||
list ?: return
|
list ?: return
|
||||||
viewModel.searchResultList.addAll(list)
|
viewModel.searchResultList.addAll(list)
|
||||||
|
|||||||
@@ -122,30 +122,37 @@ class BookSourceDebugActivity : VMBaseActivity<ActivitySourceDebugBinding, BookS
|
|||||||
}
|
}
|
||||||
initExploreKinds()
|
initExploreKinds()
|
||||||
}
|
}
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
private fun initExploreKinds() {
|
private fun initExploreKinds() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val exploreKinds = viewModel.bookSource?.exploreKinds()?.filter {
|
try {
|
||||||
!it.url.isNullOrBlank()
|
val exploreKinds = viewModel.bookSource?.exploreKinds()?.filter {
|
||||||
}
|
!it.url.isNullOrBlank()
|
||||||
exploreKinds?.firstOrNull()?.let {
|
|
||||||
binding.textFx.text = "${it.title}::${it.url}"
|
|
||||||
if (it.title.startsWith("ERROR:")) {
|
|
||||||
adapter.addItem("获取发现出错\n${it.url}")
|
|
||||||
openOrCloseHelp(false)
|
|
||||||
searchView.clearFocus()
|
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
}
|
exploreKinds?.firstOrNull()?.let {
|
||||||
@Suppress("USELESS_ELVIS")
|
binding.textFx.text = "${it.title}::${it.url}"
|
||||||
exploreKinds?.map { it.title ?: "" }?.let { exploreKindTitles ->
|
if (it.title.startsWith("ERROR:")) {
|
||||||
binding.textFx.onLongClick {
|
adapter.addItem("获取发现出错\n${it.url}")
|
||||||
selector("选择发现", exploreKindTitles) { _, index ->
|
openOrCloseHelp(false)
|
||||||
val explore = exploreKinds[index]
|
searchView.clearFocus()
|
||||||
binding.textFx.text = "${explore.title}::${explore.url}"
|
return@launch
|
||||||
searchView.setQuery(binding.textFx.text, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Suppress("USELESS_ELVIS")
|
||||||
|
exploreKinds?.map { it.title ?: "" }?.let { exploreKindTitles ->
|
||||||
|
binding.textFx.onLongClick {
|
||||||
|
selector("选择发现", exploreKindTitles) { _, index ->
|
||||||
|
val explore = exploreKinds[index]
|
||||||
|
binding.textFx.text = "${explore.title}::${explore.url}"
|
||||||
|
searchView.setQuery(binding.textFx.text, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
adapter.addItem("获取发现出错 JSON 数据错误\n$e")
|
||||||
|
openOrCloseHelp(false)
|
||||||
|
searchView.clearFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -632,6 +632,8 @@ class BookSourceEditActivity :
|
|||||||
val edit = view.editableText//获取EditText的文字
|
val edit = view.editableText//获取EditText的文字
|
||||||
if (start < 0 || start >= edit.length) {
|
if (start < 0 || start >= edit.length) {
|
||||||
edit.append(text)
|
edit.append(text)
|
||||||
|
} else if (start > end) {
|
||||||
|
edit.replace(end, start, text)
|
||||||
} else {
|
} else {
|
||||||
edit.replace(start, end, text)//光标所在位置插入文字
|
edit.replace(start, end, text)//光标所在位置插入文字
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,20 +158,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.book_source, menu)
|
menuInflater.inflate(R.menu.book_source, menu)
|
||||||
return super.onCompatCreateOptionsMenu(menu)
|
return super.onCompatCreateOptionsMenu(menu)
|
||||||
|
|||||||
@@ -198,17 +198,6 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.book_toc, menu)
|
menuInflater.inflate(R.menu.book_toc, menu)
|
||||||
this.menu = menu
|
this.menu = menu
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.DialogInterface
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.script.rhino.runScriptWithContext
|
import com.script.rhino.runScriptWithContext
|
||||||
@@ -20,6 +21,7 @@ import io.legado.app.lib.dialogs.alert
|
|||||||
import io.legado.app.ui.about.AppLogDialog
|
import io.legado.app.ui.about.AppLogDialog
|
||||||
import io.legado.app.utils.GSON
|
import io.legado.app.utils.GSON
|
||||||
import io.legado.app.utils.applyTint
|
import io.legado.app.utils.applyTint
|
||||||
|
import io.legado.app.utils.dpToPx
|
||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.openUrl
|
import io.legado.app.utils.openUrl
|
||||||
import io.legado.app.utils.printOnDebug
|
import io.legado.app.utils.printOnDebug
|
||||||
@@ -47,47 +49,51 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) {
|
|||||||
binding.toolBar.title = getString(R.string.login_source, source.getTag())
|
binding.toolBar.title = getString(R.string.login_source, source.getTag())
|
||||||
val loginInfo = source.getLoginInfoMap()
|
val loginInfo = source.getLoginInfoMap()
|
||||||
val loginUi = source.loginUi()
|
val loginUi = source.loginUi()
|
||||||
loginUi?.forEachIndexed { index, rowUi ->
|
try {
|
||||||
when (rowUi.type) {
|
loginUi?.forEachIndexed { index, rowUi ->
|
||||||
RowUi.Type.text -> ItemSourceEditBinding.inflate(
|
when (rowUi.type) {
|
||||||
layoutInflater,
|
RowUi.Type.text -> ItemSourceEditBinding.inflate(
|
||||||
binding.root,
|
layoutInflater,
|
||||||
false
|
binding.root,
|
||||||
).let {
|
false
|
||||||
binding.flexbox.addView(it.root)
|
).let {
|
||||||
it.root.id = index + 1000
|
binding.flexbox.addView(it.root)
|
||||||
it.textInputLayout.hint = rowUi.name
|
it.root.id = index + 1000
|
||||||
it.editText.setText(loginInfo?.get(rowUi.name))
|
it.textInputLayout.hint = rowUi.name
|
||||||
}
|
it.editText.setText(loginInfo?.get(rowUi.name))
|
||||||
|
}
|
||||||
|
|
||||||
RowUi.Type.password -> ItemSourceEditBinding.inflate(
|
RowUi.Type.password -> ItemSourceEditBinding.inflate(
|
||||||
layoutInflater,
|
layoutInflater,
|
||||||
binding.root,
|
binding.root,
|
||||||
false
|
false
|
||||||
).let {
|
).let {
|
||||||
binding.flexbox.addView(it.root)
|
binding.flexbox.addView(it.root)
|
||||||
it.root.id = index + 1000
|
it.root.id = index + 1000
|
||||||
it.textInputLayout.hint = rowUi.name
|
it.textInputLayout.hint = rowUi.name
|
||||||
it.editText.inputType =
|
it.editText.inputType =
|
||||||
InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
|
InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
|
||||||
it.editText.setText(loginInfo?.get(rowUi.name))
|
it.editText.setText(loginInfo?.get(rowUi.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
RowUi.Type.button -> ItemFilletTextBinding.inflate(
|
RowUi.Type.button -> ItemFilletTextBinding.inflate(
|
||||||
layoutInflater,
|
layoutInflater,
|
||||||
binding.root,
|
binding.root,
|
||||||
false
|
false
|
||||||
).let {
|
).let {
|
||||||
binding.flexbox.addView(it.root)
|
binding.flexbox.addView(it.root)
|
||||||
rowUi.style().apply(it.root)
|
rowUi.style().apply(it.root)
|
||||||
it.root.id = index + 1000
|
it.root.id = index + 1000
|
||||||
it.textView.text = rowUi.name
|
it.textView.text = rowUi.name
|
||||||
|
it.textView.setPadding(16.dpToPx())
|
||||||
it.root.onClick {
|
it.root.onClick {
|
||||||
handleButtonClick(source, rowUi, loginUi)
|
handleButtonClick(source, rowUi, loginUi)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
AppLog.put("登录UI JSON 数据错误", e, true)
|
||||||
}
|
}
|
||||||
binding.toolBar.inflateMenu(R.menu.source_login)
|
binding.toolBar.inflateMenu(R.menu.source_login)
|
||||||
binding.toolBar.menu.applyTint(requireContext())
|
binding.toolBar.menu.applyTint(requireContext())
|
||||||
|
|||||||
@@ -147,20 +147,6 @@ open class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
|
|||||||
toggleSystemBar(AppConfig.showStatusBar)
|
toggleSystemBar(AppConfig.showStatusBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||||
super.onPostCreate(savedInstanceState)
|
super.onPostCreate(savedInstanceState)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|||||||
@@ -213,28 +213,14 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
if (AppConfig.preDownloadNum == 0) return
|
if (AppConfig.preDownloadNum == 0) return
|
||||||
cacheBookJob?.cancel()
|
cacheBookJob?.cancel()
|
||||||
cacheBookJob = viewModelScope.launch(upTocPool) {
|
cacheBookJob = viewModelScope.launch(upTocPool) {
|
||||||
while (isActive) {
|
launch {
|
||||||
if (CacheBookService.isRun || !CacheBook.isRun) {
|
while (isActive && CacheBook.isRun) {
|
||||||
cacheBookJob?.cancel()
|
//有目录更新是不缓存,优先更新目录,现在更多网站限制并发
|
||||||
cacheBookJob = null
|
CacheBook.setWorkingState(waitUpTocBooks.isEmpty() && onUpTocBooks.isEmpty())
|
||||||
return@launch
|
delay(1000)
|
||||||
}
|
}
|
||||||
CacheBook.cacheBookMap.forEach {
|
|
||||||
val cacheBookModel = it.value
|
|
||||||
while (cacheBookModel.waitCount > 0) {
|
|
||||||
//有目录更新是不缓存,优先更新目录,现在更多网站限制并发
|
|
||||||
if (waitUpTocBooks.isEmpty()
|
|
||||||
&& onUpTocBooks.isEmpty()
|
|
||||||
&& CacheBook.onDownloadCount < threadCount
|
|
||||||
) {
|
|
||||||
cacheBookModel.download(this, upTocPool)
|
|
||||||
} else {
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(100)
|
|
||||||
}
|
}
|
||||||
|
CacheBook.startProcessJob(upTocPool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,20 +116,6 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
|
|||||||
observeGroupData()
|
observeGroupData()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.replace_rule, menu)
|
menuInflater.inflate(R.menu.replace_rule, menu)
|
||||||
return super.onCompatCreateOptionsMenu(menu)
|
return super.onCompatCreateOptionsMenu(menu)
|
||||||
|
|||||||
@@ -203,6 +203,8 @@ class ReplaceEditActivity :
|
|||||||
val edit = view.editableText
|
val edit = view.editableText
|
||||||
if (start < 0 || start >= edit.length) {
|
if (start < 0 || start >= edit.length) {
|
||||||
edit.append(text)
|
edit.append(text)
|
||||||
|
} else if (start > end) {
|
||||||
|
edit.replace(end, start, text)
|
||||||
} else {
|
} else {
|
||||||
edit.replace(start, end, text)
|
edit.replace(start, end, text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,6 +396,8 @@ class RssSourceEditActivity :
|
|||||||
val edit = view.editableText//获取EditText的文字
|
val edit = view.editableText//获取EditText的文字
|
||||||
if (start < 0 || start >= edit.length) {
|
if (start < 0 || start >= edit.length) {
|
||||||
edit.append(text)
|
edit.append(text)
|
||||||
|
} else if (start > end) {
|
||||||
|
edit.replace(end, start, text)
|
||||||
} else {
|
} else {
|
||||||
edit.replace(start, end, text)//光标所在位置插入文字
|
edit.replace(start, end, text)//光标所在位置插入文字
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,20 +114,6 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
|
|||||||
initSelectActionBar()
|
initSelectActionBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
currentFocus?.let {
|
|
||||||
if (it.shouldHideSoftInput(ev)) {
|
|
||||||
it.post {
|
|
||||||
it.clearFocus()
|
|
||||||
it.hideSoftInput()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.rss_source, menu)
|
menuInflater.inflate(R.menu.rss_source, menu)
|
||||||
return super.onCompatCreateOptionsMenu(menu)
|
return super.onCompatCreateOptionsMenu(menu)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.FlowCollector
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
@@ -43,7 +44,7 @@ inline fun <T> Flow<T>.onEachParallelSafe(
|
|||||||
try {
|
try {
|
||||||
action(value)
|
action(value)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
}
|
}
|
||||||
emit(value)
|
emit(value)
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ inline fun <T, R> Flow<T>.mapParallelSafe(
|
|||||||
try {
|
try {
|
||||||
emit(transform(value))
|
emit(transform(value))
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.buffer(0)
|
}.buffer(0)
|
||||||
@@ -79,7 +80,7 @@ inline fun <T, R> Flow<T>.transformParallelSafe(
|
|||||||
try {
|
try {
|
||||||
transform(value)
|
transform(value)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.buffer(0)
|
}.buffer(0)
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="16dp">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/tool_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="3dp"
|
||||||
|
android:elevation="5dp"
|
||||||
|
android:theme="?attr/actionBarStyle"
|
||||||
|
app:title="@string/server_config"
|
||||||
|
app:displayHomeAsUp="false"
|
||||||
|
app:fitStatusBar="false" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/vw_bg"
|
android:id="@+id/vw_bg"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:background="@drawable/shape_card_view"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:ignore="UselessParent">
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="3dp"
|
|
||||||
android:elevation="5dp"
|
|
||||||
android:theme="?attr/actionBarStyle"
|
|
||||||
app:title="@string/server_config"
|
|
||||||
app:displayHomeAsUp="false"
|
|
||||||
app:fitStatusBar="false" />
|
|
||||||
|
|
||||||
<io.legado.app.ui.widget.text.TextInputLayout
|
<io.legado.app.ui.widget.text.TextInputLayout
|
||||||
android:id="@+id/til_name"
|
android:id="@+id/til_name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -73,7 +73,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:padding="3dp"
|
|
||||||
app:dividerDrawable="@drawable/shape_space_divider"
|
app:dividerDrawable="@drawable/shape_space_divider"
|
||||||
app:flexDirection="row"
|
app:flexDirection="row"
|
||||||
app:flexWrap="wrap"
|
app:flexWrap="wrap"
|
||||||
@@ -82,4 +81,4 @@
|
|||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user