fix:修复词典查询弹窗问题
This commit is contained in:
@@ -263,6 +263,11 @@
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:hardwareAccelerated="true" />
|
||||
<activity
|
||||
android:name=".ui.dict.DictActivity"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:hardwareAccelerated="true" />
|
||||
<!-- 文件管理 -->
|
||||
<activity
|
||||
android:name=".ui.file.FileManageActivity"
|
||||
|
||||
@@ -20,7 +20,7 @@ import io.legado.app.ui.book.read.sheet.ChangeChapterSourceSheet
|
||||
import io.legado.app.ui.book.read.sheet.CharsetConfigSheet
|
||||
import io.legado.app.ui.book.read.sheet.ClickActionConfigSheet
|
||||
import io.legado.app.ui.book.read.sheet.ContentEditSheet
|
||||
import io.legado.app.ui.book.read.sheet.DictSheet
|
||||
import io.legado.app.ui.dict.DictSheet
|
||||
import io.legado.app.ui.book.read.sheet.DownloadSheet
|
||||
import io.legado.app.ui.book.read.sheet.EffectiveReplacesSheet
|
||||
import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
package io.legado.app.ui.book.read.sheet
|
||||
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.DictRule
|
||||
import io.legado.app.help.GlideImageGetter
|
||||
import io.legado.app.ui.dict.DictViewModel
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.utils.setHtml
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun DictSheet(
|
||||
show: Boolean,
|
||||
word: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: DictViewModel = koinViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var dictRules by remember { mutableStateOf<List<DictRule>>(emptyList()) }
|
||||
var selectedTab by remember { mutableIntStateOf(0) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
var htmlContent by remember { mutableStateOf("") }
|
||||
var emptyMessage by remember { mutableStateOf<String?>(null) }
|
||||
var glideImageGetter by remember { mutableStateOf<GlideImageGetter?>(null) }
|
||||
|
||||
LaunchedEffect(show) {
|
||||
if (!show) return@LaunchedEffect
|
||||
viewModel.initData { rules ->
|
||||
dictRules = rules
|
||||
if (rules.isEmpty()) {
|
||||
emptyMessage = context.getString(R.string.empty)
|
||||
isLoading = false
|
||||
} else {
|
||||
// Auto-select first tab
|
||||
viewModel.dict(rules[0], word) { result ->
|
||||
isLoading = false
|
||||
if (result.isBlank()) {
|
||||
emptyMessage = "没有查询到结果"
|
||||
} else {
|
||||
htmlContent = result
|
||||
emptyMessage = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = {
|
||||
glideImageGetter?.clear()
|
||||
glideImageGetter = null
|
||||
onDismissRequest()
|
||||
},
|
||||
title = word,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
) {
|
||||
// Tab row
|
||||
if (dictRules.size > 1) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = selectedTab.coerceIn(
|
||||
0,
|
||||
(dictRules.size - 1).coerceAtLeast(0)
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
edgePadding = 0.dp,
|
||||
) {
|
||||
dictRules.forEachIndexed { index, rule ->
|
||||
Tab(
|
||||
selected = selectedTab == index,
|
||||
onClick = {
|
||||
selectedTab = index
|
||||
isLoading = true
|
||||
emptyMessage = null
|
||||
viewModel.dict(rule, word) { result ->
|
||||
isLoading = false
|
||||
if (result.isBlank()) {
|
||||
emptyMessage = "没有查询到结果"
|
||||
} else {
|
||||
htmlContent = result
|
||||
emptyMessage = null
|
||||
}
|
||||
}
|
||||
},
|
||||
text = { Text(rule.name) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
when {
|
||||
isLoading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
emptyMessage != null -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = emptyMessage!!,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
TextView(ctx).apply {
|
||||
movementMethod = LinkMovementMethod()
|
||||
setPadding(32, 16, 32, 16)
|
||||
}
|
||||
},
|
||||
update = { textView ->
|
||||
textView.setHtml(htmlContent)
|
||||
glideImageGetter?.clear()
|
||||
glideImageGetter =
|
||||
GlideImageGetter.create(context, textView, htmlContent)
|
||||
textView.setHtml(htmlContent, glideImageGetter)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.legado.app.ui.dict
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.legado.app.base.BaseComposeActivity
|
||||
|
||||
class DictActivity : BaseComposeActivity() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
DictSheet(
|
||||
show = true,
|
||||
word = intent.getStringExtra(EXTRA_WORD).orEmpty(),
|
||||
onDismissRequest = { finish() },
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_WORD = "word"
|
||||
|
||||
fun startIntent(context: Context, word: String): Intent {
|
||||
return Intent(context, DictActivity::class.java)
|
||||
.putExtra(EXTRA_WORD, word)
|
||||
.apply {
|
||||
if (context !is Activity) {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.legado.app.ui.dict
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Stable
|
||||
data class DictUiState(
|
||||
val word: String = "",
|
||||
val isLoading: Boolean = false,
|
||||
val rules: ImmutableList<DictRuleUi> = persistentListOf(),
|
||||
val selectedIndex: Int = 0,
|
||||
val htmlContent: String = "",
|
||||
val emptyReason: DictEmptyReason? = null,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class DictRuleUi(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
sealed interface DictEmptyReason {
|
||||
data object BlankWord : DictEmptyReason
|
||||
data object NoRules : DictEmptyReason
|
||||
data object NoResult : DictEmptyReason
|
||||
}
|
||||
|
||||
sealed interface DictIntent {
|
||||
data class Load(val word: String) : DictIntent
|
||||
data class SelectRule(val index: Int) : DictIntent
|
||||
}
|
||||
|
||||
sealed interface DictEffect {
|
||||
data class ShowToast(val message: String) : DictEffect
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package io.legado.app.ui.dict
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseBottomSheetDialogFragment
|
||||
import io.legado.app.data.entities.DictRule
|
||||
import io.legado.app.databinding.DialogDictBinding
|
||||
import io.legado.app.help.GlideImageGetter
|
||||
import io.legado.app.utils.gone
|
||||
import io.legado.app.utils.setHtml
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import io.legado.app.utils.visible
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
/**
|
||||
* 词典
|
||||
*/
|
||||
class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
|
||||
|
||||
constructor(word: String) : this() {
|
||||
arguments = Bundle().apply {
|
||||
putString("word", word)
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModel by viewModel<DictViewModel>()
|
||||
private val binding by viewBinding(DialogDictBinding::bind)
|
||||
private var word: String? = null
|
||||
private var glideImageGetter: GlideImageGetter? = null
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.tvDict.movementMethod = LinkMovementMethod()
|
||||
word = arguments?.getString("word")
|
||||
if (word.isNullOrEmpty()) {
|
||||
toastOnUi(R.string.cannot_empty)
|
||||
dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
setupTabs()
|
||||
observeDicts()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
glideImageGetter?.clear()
|
||||
glideImageGetter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupTabs() {
|
||||
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabReselected(tab: TabLayout.Tab) = Unit
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
val dictRule = tab.tag as DictRule
|
||||
loadDict(dictRule)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun observeDicts() {
|
||||
viewModel.initData { dictRules ->
|
||||
if (dictRules.isEmpty()) {
|
||||
showEmptyView("暂无可用词典")
|
||||
return@initData
|
||||
}
|
||||
|
||||
binding.emptyMessageView.gone()
|
||||
dictRules.forEach {
|
||||
binding.tabLayout.addTab(binding.tabLayout.newTab().apply {
|
||||
text = it.name
|
||||
tag = it
|
||||
})
|
||||
}
|
||||
setupTabLayoutMode(dictRules.size)
|
||||
|
||||
|
||||
binding.tabLayout.getTabAt(0)?.select()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDict(dictRule: DictRule) {
|
||||
|
||||
binding.tvDict.gone()
|
||||
binding.emptyMessageView.gone()
|
||||
binding.rotateLoading.visible()
|
||||
|
||||
viewModel.dict(dictRule, word!!) { result ->
|
||||
binding.rotateLoading.gone()
|
||||
if (result.isBlank()) {
|
||||
showEmptyView("没有查询到结果")
|
||||
} else {
|
||||
binding.tvDict.visible()
|
||||
binding.tvDict.setHtml(result)
|
||||
glideImageGetter?.clear()
|
||||
glideImageGetter = GlideImageGetter.create(requireContext(), binding.tvDict, result)
|
||||
binding.tvDict.setHtml(result, glideImageGetter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabLayoutMode(dictCount: Int) {
|
||||
if (dictCount <= 4) {
|
||||
binding.tabLayout.tabMode = TabLayout.MODE_FIXED
|
||||
binding.tabLayout.tabGravity = TabLayout.GRAVITY_FILL
|
||||
} else {
|
||||
binding.tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
|
||||
binding.tabLayout.tabGravity = TabLayout.GRAVITY_CENTER
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEmptyView(message: String) {
|
||||
binding.tvDict.gone()
|
||||
binding.rotateLoading.gone()
|
||||
binding.emptyMessageView.visible()
|
||||
binding.emptyMessageView.setMessage(message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package io.legado.app.ui.dict
|
||||
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.GlideImageGetter
|
||||
import io.legado.app.ui.widget.components.EmptyMessage
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
|
||||
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
||||
import io.legado.app.utils.setHtml
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun DictSheet(
|
||||
show: Boolean,
|
||||
word: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: DictViewModel = koinViewModel(),
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(show, word) {
|
||||
if (show) {
|
||||
viewModel.onIntent(DictIntent.Load(word))
|
||||
}
|
||||
}
|
||||
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = word,
|
||||
) {
|
||||
DictSheetContent(
|
||||
state = state,
|
||||
onIntent = viewModel::onIntent,
|
||||
effects = viewModel.effects,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DictSheetContent(
|
||||
state: DictUiState,
|
||||
onIntent: (DictIntent) -> Unit,
|
||||
effects: Flow<DictEffect>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LaunchedEffect(effects) {
|
||||
effects.collect { effect ->
|
||||
when (effect) {
|
||||
is DictEffect.ShowToast -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DictContent(
|
||||
state = state,
|
||||
onIntent = onIntent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DictContent(
|
||||
state: DictUiState,
|
||||
onIntent: (DictIntent) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val blankWordText = stringResource(R.string.cannot_empty)
|
||||
val emptyText = stringResource(R.string.empty)
|
||||
val searchEmptyText = stringResource(R.string.search_empty)
|
||||
val tabTitles = remember(state.rules) { state.rules.map { it.name } }
|
||||
val emptyMessage = when (state.emptyReason) {
|
||||
DictEmptyReason.BlankWord -> blankWordText
|
||||
DictEmptyReason.NoRules -> emptyText
|
||||
DictEmptyReason.NoResult -> searchEmptyText
|
||||
null -> null
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (state.rules.size > 1) {
|
||||
AppTabRow(
|
||||
tabTitles = tabTitles,
|
||||
selectedTabIndex = state.selectedIndex.coerceIn(0, state.rules.lastIndex),
|
||||
onTabSelected = { index ->
|
||||
onIntent(DictIntent.SelectRule(index))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isScrollable = true,
|
||||
)
|
||||
}
|
||||
|
||||
when {
|
||||
state.isLoading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
AppCircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
emptyMessage != null -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
EmptyMessage(message = emptyMessage)
|
||||
}
|
||||
}
|
||||
|
||||
state.htmlContent.isNotBlank() -> {
|
||||
DictHtmlContent(
|
||||
htmlContent = state.htmlContent,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DictHtmlContent(
|
||||
htmlContent: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var glideImageGetter by remember { mutableStateOf<GlideImageGetter?>(null) }
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
glideImageGetter?.clear()
|
||||
glideImageGetter = null
|
||||
}
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
TextView(ctx).apply {
|
||||
movementMethod = LinkMovementMethod()
|
||||
setPadding(32, 16, 32, 16)
|
||||
}
|
||||
},
|
||||
update = { textView ->
|
||||
glideImageGetter?.clear()
|
||||
glideImageGetter = GlideImageGetter.create(context, textView, htmlContent)
|
||||
textView.setHtml(htmlContent, glideImageGetter)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
@@ -1,40 +1,112 @@
|
||||
package io.legado.app.ui.dict
|
||||
|
||||
import android.app.Application
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.data.entities.DictRule
|
||||
import io.legado.app.data.repository.DictRuleRepository
|
||||
import io.legado.app.help.coroutine.Coroutine
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DictViewModel(
|
||||
application: Application,
|
||||
private val dictRuleRepository: DictRuleRepository
|
||||
) : BaseViewModel(application) {
|
||||
) : ViewModel() {
|
||||
|
||||
private var dictJob: Coroutine<String>? = null
|
||||
private val _uiState = MutableStateFlow(DictUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
fun initData(onSuccess: (List<DictRule>) -> Unit) {
|
||||
execute {
|
||||
dictRuleRepository.getEnabled()
|
||||
}.onSuccess {
|
||||
onSuccess.invoke(it)
|
||||
private val _effects = MutableSharedFlow<DictEffect>(extraBufferCapacity = 16)
|
||||
val effects = _effects.asSharedFlow()
|
||||
|
||||
private var dictRules: List<DictRule> = emptyList()
|
||||
private var dictJob: Job? = null
|
||||
|
||||
fun onIntent(intent: DictIntent) {
|
||||
when (intent) {
|
||||
is DictIntent.Load -> load(intent.word)
|
||||
is DictIntent.SelectRule -> selectRule(intent.index)
|
||||
}
|
||||
}
|
||||
|
||||
fun dict(
|
||||
dictRule: DictRule,
|
||||
word: String,
|
||||
onFinally: (String) -> Unit
|
||||
) {
|
||||
private fun load(word: String) {
|
||||
dictJob?.cancel()
|
||||
dictJob = execute {
|
||||
dictRule.search(word)
|
||||
}.onSuccess {
|
||||
onFinally.invoke(it)
|
||||
}.onError {
|
||||
onFinally.invoke(it.localizedMessage ?: "ERROR")
|
||||
val query = word.trim()
|
||||
_uiState.value = DictUiState(
|
||||
word = query,
|
||||
isLoading = query.isNotBlank(),
|
||||
emptyReason = if (query.isBlank()) DictEmptyReason.BlankWord else null,
|
||||
)
|
||||
if (query.isBlank()) return
|
||||
|
||||
viewModelScope.launch {
|
||||
dictRules = withContext(Dispatchers.IO) {
|
||||
dictRuleRepository.getEnabled()
|
||||
}
|
||||
_uiState.update { state ->
|
||||
state.copy(
|
||||
rules = dictRules.map { DictRuleUi(it.name) }.toImmutableList(),
|
||||
isLoading = dictRules.isNotEmpty(),
|
||||
emptyReason = if (dictRules.isEmpty()) DictEmptyReason.NoRules else null,
|
||||
)
|
||||
}
|
||||
if (dictRules.isNotEmpty()) {
|
||||
searchRule(index = 0, word = query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectRule(index: Int) {
|
||||
val state = _uiState.value
|
||||
if (index !in dictRules.indices || state.selectedIndex == index && state.isLoading) return
|
||||
searchRule(index = index, word = state.word)
|
||||
}
|
||||
|
||||
private fun searchRule(index: Int, word: String) {
|
||||
dictJob?.cancel()
|
||||
val rule = dictRules.getOrNull(index) ?: return
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedIndex = index,
|
||||
isLoading = true,
|
||||
htmlContent = "",
|
||||
emptyReason = null,
|
||||
)
|
||||
}
|
||||
dictJob = viewModelScope.launch {
|
||||
val result = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
rule.search(word)
|
||||
}
|
||||
}.getOrElse { error ->
|
||||
error.localizedMessage ?: "ERROR"
|
||||
}
|
||||
_uiState.update {
|
||||
if (result.isBlank()) {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
htmlContent = "",
|
||||
emptyReason = DictEmptyReason.NoResult,
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
htmlContent = result,
|
||||
emptyReason = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
dictJob?.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,13 @@ import android.view.ViewGroup
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.dict.DictDialog
|
||||
import io.legado.app.ui.dict.DictActivity
|
||||
import io.legado.app.utils.toastOnUi
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -83,7 +82,7 @@ class VisibleWebView(
|
||||
postDelayed({
|
||||
getSelectedText { selectedText ->
|
||||
if (selectedText.isNotBlank()) {
|
||||
showDictDialog(selectedText)
|
||||
showDictSheet(selectedText)
|
||||
} else {
|
||||
context.toastOnUi("未获取到选中文本,请重试")
|
||||
}
|
||||
@@ -124,12 +123,8 @@ class VisibleWebView(
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDictDialog(selectedText: String) {
|
||||
val activity = context as? AppCompatActivity ?: return
|
||||
val dialog = DictDialog(selectedText)
|
||||
activity.supportFragmentManager.beginTransaction()
|
||||
.add(dialog, "DictDialog")
|
||||
.commitAllowingStateLoss()
|
||||
private fun showDictSheet(selectedText: String) {
|
||||
context.startActivity(DictActivity.startIntent(context, selectedText))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="16dp"
|
||||
android:minHeight="0dp"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dict"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/header_layout" />
|
||||
|
||||
<com.google.android.material.loadingindicator.LoadingIndicator
|
||||
android:id="@+id/rotate_loading"
|
||||
style="@style/Widget.Material3.LoadingIndicator.Contained"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tab_layout" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tab_layout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_dict"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<io.legado.app.ui.widget.EmptyMessageView
|
||||
android:id="@+id/empty_message_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:messageText="词典内容为空!"/>
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user