diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index 52c43cee8..c47ad30ea 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -35,6 +35,7 @@ object EventBus { const val UP_MANGA_CONFIG = "upMangaConfig" const val PLAY_MODE_CHANGED = "playModeChanged" const val REFRESH_BOOK_INFO = "refreshBookInfo" + const val REFRESH_BOOK_TOC = "refreshBookToc" const val REFRESH_BOOK_CONTENT = "refreshBookContent" const val UP_TOC = "upToc" diff --git a/app/src/main/java/io/legado/app/domain/usecase/ExploreKindUiUseCase.kt b/app/src/main/java/io/legado/app/domain/usecase/ExploreKindUiUseCase.kt index d19da8344..fdc2fa08e 100644 --- a/app/src/main/java/io/legado/app/domain/usecase/ExploreKindUiUseCase.kt +++ b/app/src/main/java/io/legado/app/domain/usecase/ExploreKindUiUseCase.kt @@ -72,8 +72,8 @@ class ExploreKindUiUseCase( activity = activity, source = source, callback = object : SourceLoginJsExtensions.Callback { - override fun upUiData(data: Map?) = Unit - override fun reUiView() = onRefreshKinds() + override fun upUiData(data: Map?) = Unit + override fun reUiView(deltaUp: Boolean) = onRefreshKinds() } ) withContext(Dispatchers.IO) { diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index 2660ea21b..cd995e54f 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -74,6 +74,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update @@ -99,6 +102,35 @@ class BookInfoViewModel( private val _effects = MutableSharedFlow(extraBufferCapacity = 8) val effects = _effects.asSharedFlow() + init { + collectEventBus() + } + + private fun collectEventBus() { + viewModelScope.launch { + eventFlow(EventBus.REFRESH_BOOK_INFO).collect { + currentBook?.let { book -> + refreshBook(book) + } + } + } + viewModelScope.launch { + eventFlow(EventBus.REFRESH_BOOK_TOC).collect { + currentBook?.let { book -> + loadChapter(book) + } + } + } + } + + private inline fun eventFlow(tag: String): Flow = callbackFlow { + val obs = androidx.lifecycle.Observer { trySend(it) } + com.jeremyliao.liveeventbus.LiveEventBus.get(tag).observeForever(obs) + awaitClose { + com.jeremyliao.liveeventbus.LiveEventBus.get(tag).removeObserver(obs) + } + } + private var currentBook: Book? = null set(value) { field = value diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index c6e417a3e..eae76382d 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -1,11 +1,13 @@ package io.legado.app.ui.book.source.edit import android.content.Intent +import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.widget.EditText import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.lifecycle.lifecycleScope import com.google.android.material.tabs.TabLayout import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel @@ -24,9 +26,7 @@ import io.legado.app.help.config.LocalConfig import io.legado.app.lib.dialogs.SelectItem import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.selector -//import io.legado.app.lib.theme.accentColor -//import io.legado.app.lib.theme.backgroundColor -//import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.about.AppLogDialog import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchScope import io.legado.app.ui.book.source.debug.BookSourceDebugActivity @@ -146,6 +146,7 @@ class BookSourceEditActivity : ErrorCorrectionLevel.L ) + R.id.menu_log -> showDialogFragment() R.id.menu_help -> showHelp("ruleHelp") R.id.menu_login -> viewModel.save(getSource()) { source -> startActivity { @@ -261,6 +262,8 @@ class BookSourceEditActivity : else -> 0 } ) + binding.cbIsEventListener.isChecked = it.eventListener + binding.cbIsCustomButton.isChecked = it.customButton } // 基本信息 sourceEntities.clear() @@ -361,6 +364,7 @@ class BookSourceEditActivity : add(EditEntity("imageStyle", cr.imageStyle, R.string.rule_image_style)) add(EditEntity("imageDecode", cr.imageDecode, R.string.rule_image_decode)) add(EditEntity("payAction", cr.payAction, R.string.rule_pay_action)) + add(EditEntity("callBackJs", cr.callBackJs, R.string.rule_call_back)) } // val rr = bs.getReviewRule() @@ -392,6 +396,8 @@ class BookSourceEditActivity : 1 -> BookSourceType.audio else -> BookSourceType.default } + source.eventListener = binding.cbIsEventListener.isChecked + source.customButton = binding.cbIsCustomButton.isChecked val searchRule = SearchRule() val exploreRule = ExploreRule() val bookInfoRule = BookInfoRule() @@ -559,6 +565,7 @@ class BookSourceEditActivity : "imageStyle" -> contentRule.imageStyle = it.value "imageDecode" -> contentRule.imageDecode = it.value "payAction" -> contentRule.payAction = it.value + "callBackJs" -> contentRule.callBackJs = it.value } } // reviewEntities.forEach { @@ -592,6 +599,22 @@ class BookSourceEditActivity : return source } + @RequiresApi(Build.VERSION_CODES.M) + override fun onUndoClicked() { + val editText = window.decorView.findFocus() + if (editText is EditText) { + editText.onTextContextMenuItem(android.R.id.undo) + } + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onRedoClicked() { + val editText = window.decorView.findFocus() + if (editText is EditText) { + editText.onTextContextMenuItem(android.R.id.redo) + } + } + private fun alertGroups() { lifecycleScope.launch { val groups = withContext(IO) { @@ -643,18 +666,44 @@ class BookSourceEditActivity : } override fun sendText(text: String) { - if (text.isBlank()) return val view = window.decorView.findFocus() if (view is EditText) { - val start = view.selectionStart - val end = view.selectionEnd - val edit = view.editableText//获取EditText的文字 - if (start < 0 || start >= edit.length) { - edit.append(text) - } else if (start > end) { - edit.replace(end, start, text) - } else { - edit.replace(start, end, text)//光标所在位置插入文字 + var start = view.selectionStart + var end = view.selectionEnd + if (start > end) { + val temp = start + start = end + end = temp + } + if (text.isNotEmpty()) { + val edit = view.editableText//获取EditText的文字 + if (start < 0 || start >= edit.length) { + edit.append(text) + } else { + edit.replace(start, end, text)//光标所在位置插入文字 + } + } + if (adapter.editEntityMaxLine >= 999) { + view.post { + val editTextLocation = IntArray(2) + view.getLocationOnScreen(editTextLocation) + val recyclerViewLocation = IntArray(2) + binding.recyclerView.getLocationOnScreen(recyclerViewLocation) + val layout = view.layout + if (layout != null) { + val line = layout.getLineForOffset(end) + val cursorYInEditText = layout.getLineTop(line) + val cursorYOnScreen = editTextLocation[1] + cursorYInEditText + val cursorYInRecyclerView = cursorYOnScreen - recyclerViewLocation[1] + val recyclerViewBottom = binding.recyclerView.height - 120 + if (cursorYInRecyclerView !in 0..recyclerViewBottom) { + val scrollDistance = cursorYInRecyclerView - recyclerViewBottom / 3 + if (scrollDistance > 0 && binding.recyclerView.canScrollVertically(1) || scrollDistance < 0 && binding.recyclerView.canScrollVertically(-1)) { + binding.recyclerView.smoothScrollBy(0, scrollDistance) + } + } + } + } } } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt index a629f497d..ce4fbce93 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt @@ -7,6 +7,7 @@ import io.legado.app.base.BaseViewModel import io.legado.app.data.appDb import io.legado.app.data.entities.BookSource import io.legado.app.exception.NoStackTraceException +import io.legado.app.help.ConcurrentRateLimiter.Companion.concurrentRecordMap import io.legado.app.help.RuleComplete import io.legado.app.help.config.SourceConfig import io.legado.app.help.http.CookieStore @@ -73,6 +74,7 @@ class BookSourceEditViewModel(application: Application) : BaseViewModel(applicat } appDb.bookSourceDao.insert(source) bookSource = source + concurrentRecordMap.remove(source.bookSourceUrl) source }.onSuccess { success?.invoke(it) diff --git a/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt b/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt index b9de6c6a9..d8b662755 100644 --- a/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt +++ b/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt @@ -70,22 +70,22 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { viewModel.source, viewModel.bookType, callback = object : SourceLoginJsExtensions.Callback { - override fun upUiData(data: Map?) { + override fun upUiData(data: Map?) { activity?.runOnUiThread { // 在主线程中更新 UI handleUpUiData(data) } } - override fun reUiView() { + override fun reUiView(deltaUp: Boolean) { activity?.runOnUiThread { - handleReUiView() + handleReUiView(deltaUp) } } } ) } - private fun handleReUiView() { + private fun handleReUiView(deltaUp: Boolean = false) { val source = viewModel.source ?: return val loginUiStr = source.loginUi ?: return val codeStr = loginUiStr.let { @@ -100,18 +100,16 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { lifecycleScope.launch(Main) { val loginUiJson = evalUiJs(codeStr) rowUis = loginUi(loginUiJson) - binding.flexbox.removeAllViews() - rowUiBuilder(source, rowUis) + rowUiBuilder(source, rowUis, deltaUp) } } else { rowUis = loginUi(loginUiStr) - binding.flexbox.removeAllViews() - rowUiBuilder(source, rowUis) + rowUiBuilder(source, rowUis, deltaUp) } } @SuppressLint("SetTextI18n") - private fun handleUpUiData(data: Map?) { + private fun handleUpUiData(data: Map?) { hasChange = true if (data == null) { val newLoginInfo: MutableMap = mutableMapOf() @@ -163,22 +161,22 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { if (index != -1) { when (val rowView = binding.root.findViewById(index + 1000)) { is TextInputLayout -> { - val value = value ?: run { + val textValue = value?.toString() ?: run { val rowUi = rowUis?.getOrNull(index) ?: return@forEach rowUi.default ?: "" } - rowView.editText?.setText(value) + rowView.editText?.setText(textValue) } is TextView -> { val rowUi = rowUis?.getOrNull(index) ?: return@forEach when (rowUi.type) { Type.button -> { - rowView.text = value ?: key + rowView.text = value?.toString() ?: key } Type.toggle -> { - val char = value ?: run { + val char = value?.toString() ?: run { val chars = rowUi.chars?.filterNotNull() ?: listOf("chars is null") chars.getOrNull(0) ?: "" @@ -194,8 +192,9 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { is LinearLayout -> { val rowUi = rowUis?.getOrNull(index) ?: return@forEach val items = rowUi.chars?.filterNotNull() ?: listOf("chars", "is null") - val index = items.indexOf(value) - loginInfo[rowUi.name] = value ?: run { + val strValue = value?.toString() + val index = items.indexOf(strValue) + loginInfo[rowUi.name] = strValue ?: run { items.getOrNull(0) ?: "" } rowView.findViewById(R.id.sp_type) @@ -203,7 +202,7 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { } } } else { - loginInfo[key] = value ?: "" + loginInfo[key] = value?.toString() ?: "" } } } @@ -240,9 +239,12 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { } @SuppressLint("SetTextI18n", "ClickableViewAccessibility") - private fun rowUiBuilder(source: BaseSource, rowUis: List?) { + private fun rowUiBuilder(source: BaseSource, rowUis: List?, deltaUp: Boolean = false) { val loginInfo = viewModel.loginInfo - rowUiName.clear() + if (!deltaUp) { + binding.flexbox.removeAllViews() + rowUiName.clear() + } rowUis?.forEachIndexed { index, rowUi -> val type = rowUi.type val name = rowUi.name diff --git a/app/src/main/java/io/legado/app/ui/login/SourceLoginJsExtensions.kt b/app/src/main/java/io/legado/app/ui/login/SourceLoginJsExtensions.kt index ee27eac48..0d17eea9a 100644 --- a/app/src/main/java/io/legado/app/ui/login/SourceLoginJsExtensions.kt +++ b/app/src/main/java/io/legado/app/ui/login/SourceLoginJsExtensions.kt @@ -27,16 +27,17 @@ class SourceLoginJsExtensions( ) : RssJsExtensions(activity, source) { private val callbackRef: WeakReference = WeakReference(callback) interface Callback { - fun upUiData(data: Map?) - fun reUiView() + fun upUiData(data: Map?) + fun reUiView(deltaUp: Boolean = false) } - fun upLoginData(data: Map?) { + fun upLoginData(data: Map?) { callbackRef.get()?.upUiData(data) } - fun reLoginView() { - callbackRef.get()?.reUiView() + @JvmOverloads + fun reLoginView(deltaUp: Boolean = false) { + callbackRef.get()?.reUiView(deltaUp) } fun refreshExplore() { @@ -47,6 +48,10 @@ class SourceLoginJsExtensions( postEvent(EventBus.REFRESH_BOOK_INFO, true) } + fun refreshBookToc() { + postEvent(EventBus.REFRESH_BOOK_TOC, true) + } + fun refreshContent() { postEvent(EventBus.REFRESH_BOOK_CONTENT, true) } diff --git a/app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt b/app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt index 12184e85a..fb67fb3de 100644 --- a/app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt +++ b/app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt @@ -175,6 +175,8 @@ class KeyboardToolPop( fun sendText(text: String) + fun onUndoClicked() {} + fun onRedoClicked() {} } } diff --git a/app/src/main/res/layout/activity_book_source_edit.xml b/app/src/main/res/layout/activity_book_source_edit.xml index cb379ff40..b2691c345 100644 --- a/app/src/main/res/layout/activity_book_source_edit.xml +++ b/app/src/main/res/layout/activity_book_source_edit.xml @@ -73,6 +73,38 @@ + + + + + + + + + + + + Enable record Copy all Automatic completion + EventListener + Custom Button + callBackJs Sort by size Launch interface style Startup screen image and whether to display text, etc.