diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 2f1ab8941..8612ca5ad 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -274,6 +274,7 @@ object PreferKey { const val bookshelfGroupListStyle = "bookshelfGroupListStyle" const val bookshelfGroupCoverCount = "bookshelfGroupCoverCount" const val bookshelfListCoverWidth = "bookshelfListCoverWidth" + const val bookshelfGridCoverWidth = "bookshelfGridCoverWidth" const val exploreLayoutGridLandscape = "exploreLayoutGridLandscape" diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt index ab8f1f178..b65125cd6 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt @@ -13,7 +13,6 @@ import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.core.os.bundleOf import androidx.core.view.isInvisible -import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.lifecycleScope @@ -34,6 +33,7 @@ import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.alert import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.ReadBookActivity +import io.legado.app.ui.book.search.SearchScope import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.widget.dialog.WaitDialog @@ -83,14 +83,15 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b private var currentSelectedSearchBook: SearchBook? = null private val searchFinishCallback: (isEmpty: Boolean) -> Unit = { if (it) { - val searchGroup = AppConfig.searchGroup - if (searchGroup.isNotEmpty()) { + val searchScope = SearchScope(ChangeSourceConfig.searchScope) + val group = searchScope.display + if (!searchScope.isAll()) { lifecycleScope.launch { context?.alert("搜索结果为空") { - setMessage("${searchGroup}分组搜索结果为空,是否切换到全部分组") + setMessage("${group}分组搜索结果为空,是否切换到全部分组") cancelButton() okButton { - AppConfig.searchGroup = "" + ChangeSourceConfig.searchScope = "" upGroupMenuName() viewModel.startSearch() } @@ -343,9 +344,9 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b else -> if (item?.groupId == R.id.source_group && !item.isChecked) { item.isChecked = true if (item.title.toString() == getString(R.string.all_source)) { - AppConfig.searchGroup = "" + ChangeSourceConfig.searchScope = "" } else { - AppConfig.searchGroup = item.title.toString() + ChangeSourceConfig.searchScope = item.title.toString() } upGroupMenuName() lifecycleScope.launch(IO) { @@ -493,7 +494,8 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b private fun upGroupMenu() { binding.toolBar.menu.findItem(R.id.menu_group)?.run { subMenu?.transaction { menu -> - val selectedGroup = AppConfig.searchGroup + val searchScope = SearchScope(ChangeSourceConfig.searchScope) + val selectedGroup = searchScope.displayNames.firstOrNull() ?: "" menu.removeGroup(R.id.source_group) val allItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source) var hasSelectedGroup = false @@ -507,7 +509,7 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b } menu.setGroupCheckable(R.id.source_group, true, true) if (hasSelectedGroup) { - title = getString(R.string.group) + "(" + AppConfig.searchGroup + ")" + title = getString(R.string.group) + "(" + selectedGroup + ")" } else { allItem.isChecked = true title = getString(R.string.group) @@ -521,11 +523,11 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b */ private fun upGroupMenuName() { val menuGroup = binding.toolBar.menu.findItem(R.id.menu_group) - val searchGroup = AppConfig.searchGroup - if (searchGroup.isEmpty()) { + val searchScope = SearchScope(ChangeSourceConfig.searchScope) + if (searchScope.isAll()) { menuGroup?.title = getString(R.string.group) } else { - menuGroup?.title = getString(R.string.group) + "($searchGroup)" + menuGroup?.title = getString(R.string.group) + "(${searchScope.display})" } } diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt index 561b0153c..4451c44ba 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt @@ -38,7 +38,6 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -411,25 +410,24 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a } private fun getDbSearchBooks(): List { + val searchScope = io.legado.app.ui.book.search.SearchScope( + ChangeSourceConfig.searchScope + ) + val group = when { + searchScope.isAll() || searchScope.isSource() -> "" + else -> searchScope.displayNames.firstOrNull() ?: "" + } return if (screenKey.isEmpty()) { if (AppConfig.changeSourceCheckAuthor) { - appDb.searchBookDao.changeSourceByGroup( - name, author, AppConfig.searchGroup - ) + appDb.searchBookDao.changeSourceByGroup(name, author, group) } else { - appDb.searchBookDao.changeSourceByGroup( - name, "", AppConfig.searchGroup - ) + appDb.searchBookDao.changeSourceByGroup(name, "", group) } } else { if (AppConfig.changeSourceCheckAuthor) { - appDb.searchBookDao.changeSourceSearch( - name, author, screenKey, AppConfig.searchGroup - ) + appDb.searchBookDao.changeSourceSearch(name, author, screenKey, group) } else { - appDb.searchBookDao.changeSourceSearch( - name, "", screenKey, AppConfig.searchGroup - ) + appDb.searchBookDao.changeSourceSearch(name, "", screenKey, group) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt index 2b68e12aa..2411d58bd 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.book.changesource +//import io.legado.app.lib.theme.primaryColor import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -13,7 +14,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R @@ -29,8 +29,8 @@ import io.legado.app.databinding.DialogChapterChangeSourceBinding import io.legado.app.help.book.BookHelp import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.alert -//import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.book.read.ReadBookActivity +import io.legado.app.ui.book.search.SearchScope import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.widget.recycler.VerticalDivider @@ -88,14 +88,15 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c private var searchBook: SearchBook? = null private val searchFinishCallback: (isEmpty: Boolean) -> Unit = { if (it) { - val searchGroup = AppConfig.searchGroup - if (searchGroup.isNotEmpty()) { + val searchScope = SearchScope(ChangeSourceConfig.searchScope) + val group = searchScope.display + if (!searchScope.isAll()) { lifecycleScope.launch { context?.alert("搜索结果为空") { - setMessage("${searchGroup}分组搜索结果为空,是否切换到全部分组") + setMessage("${group}分组搜索结果为空,是否切换到全部分组") noButton() yesButton { - AppConfig.searchGroup = "" + ChangeSourceConfig.searchScope = "" upGroupMenu() viewModel.startSearch() } @@ -282,9 +283,9 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c else -> if (item?.groupId == R.id.source_group && !item.isChecked) { item.isChecked = true if (item.title.toString() == getString(R.string.all_source)) { - AppConfig.searchGroup = "" + ChangeSourceConfig.searchScope = "" } else { - AppConfig.searchGroup = item.title.toString() + ChangeSourceConfig.searchScope = item.title.toString() } lifecycleScope.launch(IO) { viewModel.stopSearch() @@ -380,7 +381,8 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c */ private fun upGroupMenu() { binding.toolBar.menu.findItem(R.id.menu_group)?.subMenu?.transaction { menu -> - val selectedGroup = AppConfig.searchGroup + val searchScope = SearchScope(ChangeSourceConfig.searchScope) + val selectedGroup = searchScope.displayNames.firstOrNull() ?: "" menu.removeGroup(R.id.source_group) val allItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source) var hasSelectedGroup = false diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceConfig.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceConfig.kt index 993ba412e..f4aa05526 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceConfig.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceConfig.kt @@ -6,11 +6,6 @@ import io.legado.app.ui.config.prefDelegate object ChangeSourceConfig { - var searchGroup by prefDelegate( - key = "searchGroup", - defaultValue = "" - ) - var searchScope by prefDelegate( key = "changeSourceSearchScope", defaultValue = "" diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt index 74aebae4f..148de9cb9 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt @@ -35,8 +35,6 @@ import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Timeline import androidx.compose.material.icons.outlined.Book -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExtendedFloatingActionButton @@ -938,16 +936,12 @@ private fun BookInfoSummary( } Spacer(modifier = Modifier.height(4.dp)) book.remark?.takeIf { it.isNotBlank() }?.let { remark -> - ElevatedCard( + GlassCard( modifier = Modifier.fillMaxWidth(), onClick = onRemarkClick, - colors = CardDefaults.elevatedCardColors( - containerColor = LegadoTheme.colorScheme.surfaceContainerHigh, - contentColor = LegadoTheme.colorScheme.onSurface, - ) + containerColor = LegadoTheme.colorScheme.surfaceContainerLow, ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { - AppText(text = stringResource(R.string.book_remark), style = LegadoTheme.typography.titleSmall) AppText(text = remark, style = LegadoTheme.typography.labelMediumEmphasized) } } diff --git a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt index 6edc1b74a..77183c93c 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt @@ -760,7 +760,7 @@ fun ChapterItem( text = item.title, style = LegadoTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium), color = textColor, - maxLines = 1, + maxLines = 2, overflow = TextOverflow.Ellipsis ) } @@ -779,7 +779,7 @@ fun ChapterItem( val showStatusIcon = remember(item.isDur, item.downloadState, item.wordCount, showWordCount) { if (item.downloadState == DownloadState.LOCAL) { - showWordCount && !item.wordCount.isNullOrEmpty() + item.isDur || (showWordCount && !item.wordCount.isNullOrEmpty()) } else { true } @@ -888,15 +888,12 @@ private fun StatusIcon( ) { val targetState = when { - downloadState == DownloadState.LOCAL -> { - if (showWordCount && !wordCount.isNullOrEmpty()) "SUCCESS_WORD_COUNT" else "EMPTY" - } - + showWordCount && !wordCount.isNullOrEmpty() && (downloadState == DownloadState.LOCAL || downloadState == DownloadState.SUCCESS) -> "SUCCESS_WORD_COUNT" isDur -> "DUR" downloadState == DownloadState.DOWNLOADING -> "LOADING" - downloadState == DownloadState.SUCCESS && showWordCount && !wordCount.isNullOrEmpty() -> "SUCCESS_WORD_COUNT" downloadState == DownloadState.SUCCESS -> "SUCCESS_ICON" downloadState == DownloadState.ERROR -> "ERROR" + downloadState == DownloadState.LOCAL -> "EMPTY" else -> "NONE" } @@ -933,7 +930,7 @@ private fun StatusIcon( "SUCCESS_WORD_COUNT" -> { NormalCard( cornerRadius = 12.dp, - containerColor = LegadoTheme.colorScheme.surfaceContainer + containerColor = if (isDur) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer ) { if (wordCount != null) { AppText( @@ -941,7 +938,7 @@ private fun StatusIcon( .padding(horizontal = 8.dp, vertical = 4.dp), text = wordCount, style = LegadoTheme.typography.labelSmall.copy(fontSize = 9.sp), - color = LegadoTheme.colorScheme.onSurfaceVariant + color = if (isDur) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant ) } } diff --git a/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt b/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt index 4dd49853c..96019d9a5 100644 --- a/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid @@ -23,6 +24,9 @@ import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.filled.GridView @@ -33,6 +37,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -227,14 +232,24 @@ fun HomepageScreen( data = errorMsg, onDismissRequest = { errorMsg = null }, title = stringResource(R.string.homepage_module_error), - text = errorMsg, confirmText = stringResource(R.string.copy_text), onConfirm = { context.sendToClip(it) errorMsg = null }, dismissText = stringResource(R.string.close), - onDismiss = { errorMsg = null } + onDismiss = { errorMsg = null }, + content = { msg -> + SelectionContainer { + Text( + text = msg, + style = LegadoTheme.typography.bodyMedium, + modifier = Modifier + .heightIn(max = 400.dp) + .verticalScroll(rememberScrollState()) + ) + } + } ) HomepageModuleManageSheet( diff --git a/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt b/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt index d1950c69a..eefb363e0 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt @@ -7,11 +7,16 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -52,14 +57,24 @@ fun LoadMoreFooter( data = showFullError, onDismissRequest = { showFullError = null }, title = "错误详情", - textProvider = { this }, confirmText = "复制", onConfirm = { error -> context.sendToClip(error) showFullError = null }, dismissText = "关闭", - onDismiss = { showFullError = null } + onDismiss = { showFullError = null }, + content = { error -> + SelectionContainer { + Text( + text = error, + style = LegadoTheme.typography.bodyMedium, + modifier = Modifier + .heightIn(max = 400.dp) + .verticalScroll(rememberScrollState()) + ) + } + } ) Box( diff --git a/app/src/main/java/io/legado/app/ui/widget/components/SearchBar.kt b/app/src/main/java/io/legado/app/ui/widget/components/SearchBar.kt index e1070ecff..ea7d86685 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/SearchBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/SearchBar.kt @@ -12,8 +12,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -55,12 +59,14 @@ fun SearchBar( val textFieldState = rememberTextFieldState(initialText = query) val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current + var hasFocused by rememberSaveable { mutableStateOf(false) } LaunchedEffect(autoFocus) { - if (autoFocus) { + if (autoFocus && !hasFocused) { focusRequester.requestFocus() // 某些情况下需要手动调用 show() 确保键盘弹出 keyboardController?.show() + hasFocused = true } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt b/app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt index 7bb04b265..11fb1bc5f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/alert/AppAlertDialog.kt @@ -54,6 +54,7 @@ fun AppAlertDialog( modifier = Modifier .fillMaxWidth() .padding(top = 12.dp) + .verticalScroll(rememberScrollState()) ) { content() } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindItemState.kt b/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindItemState.kt index 9f31909d6..06cb12304 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindItemState.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindItemState.kt @@ -13,6 +13,7 @@ import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.domain.usecase.ExploreKindUiUseCase import io.legado.app.help.source.getExploreInfoMap import io.legado.app.utils.InfoMap +import io.legado.app.utils.showDialogFragment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch @@ -68,6 +69,10 @@ class ExploreKindItemState( ?: kind.title } } + + fun showError(error: String) { + activity?.showDialogFragment(io.legado.app.ui.widget.dialog.TextDialog("ERROR", error)) + } } @Composable diff --git a/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindMultiTypeItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindMultiTypeItem.kt index 9efed4f4a..3d363990a 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindMultiTypeItem.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindMultiTypeItem.kt @@ -1,9 +1,14 @@ package io.legado.app.ui.widget.components.explore +import android.content.Context +import android.content.ContextWrapper import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Refresh @@ -18,17 +23,19 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.domain.usecase.ExploreKindUiUseCase import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem -import io.legado.app.ui.widget.dialog.TextDialog -import io.legado.app.utils.showDialogFragment +import io.legado.app.ui.widget.components.text.AppText +import io.legado.app.utils.sendToClip import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -52,84 +59,55 @@ fun ExploreKindMultiTypeItem( onClick: (() -> Unit)? = null, content: (@Composable (displayName: String, isSelected: Boolean, onClick: () -> Unit, trailingIcon: @Composable (() -> Unit)?) -> Unit)? = null ) { - val state = rememberExploreKindItemState(kind, sourceUrl, useCase, activity, onRefreshKinds) + val context = LocalContext.current + val currentActivity = remember(context, activity) { activity ?: context.findActivity() } + val state = + rememberExploreKindItemState(kind, sourceUrl, useCase, currentActivity, onRefreshKinds) state.ResolveDisplayName(displayNameOverride) + var showFullError by remember { mutableStateOf(null) } + val errorMsg = remember(kind) { kind.errorMsg } val trailingIcon = rememberTrailingIcon(kind.type, isSelected) - if (onClick != null) { + val BaseItem = @Composable { text: String, click: () -> Unit -> if (content != null) { - content(state.displayName, isSelected, onClick, trailingIcon) + content(text, isSelected, click, trailingIcon) } else { ExploreKindItem( kind = kind, isClickable = true, - onClick = onClick, + onClick = click, modifier = modifier, backgroundColor = backgroundColor, isMiuix = isMiuix, - displayText = state.displayName, + displayText = text, isSelected = isSelected, trailingIcon = trailingIcon ) } - return } - when (kind.type) { - ExploreKind.Type.url -> { - val url = kind.url?.takeIf { it.isNotBlank() } - val internalOnClick = { - if (!url.isNullOrBlank()) { - if (kind.title.startsWith("ERROR:")) { - activity?.showDialogFragment(TextDialog("ERROR", url)) - } else { - onOpenUrl(url) - } + when { + errorMsg != null -> BaseItem(state.displayName) { showFullError = errorMsg } + + onClick != null -> BaseItem(state.displayName, onClick) + + else -> when (kind.type) { + ExploreKind.Type.url -> { + val url = kind.url?.takeIf { it.isNotBlank() } + BaseItem(state.displayName) { + if (!url.isNullOrBlank()) onOpenUrl(url) } } - if (content != null) { - content(state.displayName, isSelected, internalOnClick, trailingIcon) - } else { - ExploreKindItem( - kind = kind, - isClickable = !url.isNullOrBlank(), - onClick = internalOnClick, - modifier = modifier, - backgroundColor = backgroundColor, - isMiuix = isMiuix, - displayText = state.displayName, - isSelected = isSelected - ) - } - } - ExploreKind.Type.button -> { - val internalOnClick = { - if (onRunAction != null) onRunAction() - else state.executeAction(kind.action) + ExploreKind.Type.button -> { + BaseItem(state.displayName) { + if (onRunAction != null) onRunAction() + else state.executeAction(kind.action) + } } - if (content != null) { - content(state.displayName, isSelected, internalOnClick, trailingIcon) - } else { - ExploreKindItem( - kind = kind, - isClickable = !kind.action.isNullOrBlank(), - onClick = internalOnClick, - modifier = modifier, - backgroundColor = backgroundColor, - isMiuix = isMiuix, - displayText = state.displayName, - isSelected = isSelected, - trailingIcon = trailingIcon - ) - } - } - ExploreKind.Type.text -> { - if (content != null) { - content(state.displayName, isSelected, {}, null) - } else { + ExploreKind.Type.text -> { TextTypeItem( kind, sourceUrl, @@ -140,56 +118,73 @@ fun ExploreKindMultiTypeItem( backgroundColor ) } - } - ExploreKind.Type.toggle -> { - ToggleTypeItem( - kind, - sourceUrl, - state, - valueOverride, - onValueChange, - isSelected, - modifier, - backgroundColor, - isMiuix, - trailingIcon, - content - ) - } + ExploreKind.Type.toggle -> { + ToggleTypeItem(kind, sourceUrl, state, valueOverride, onValueChange, BaseItem) + } - ExploreKind.Type.select -> { - SelectTypeItem( - kind, - sourceUrl, - state, - valueOverride, - onValueChange, - isSelected, - modifier, - backgroundColor, - isMiuix, - trailingIcon, - content - ) - } + ExploreKind.Type.select -> { + SelectTypeItem(kind, sourceUrl, state, valueOverride, onValueChange, BaseItem) + } - else -> { - if (content != null) { - content(state.displayName, isSelected, {}, null) - } else { - ExploreKindItem( - kind = kind, - isClickable = false, - onClick = {}, - modifier = modifier, - backgroundColor = backgroundColor, - isMiuix = isMiuix, - displayText = state.displayName - ) + else -> { + if (content != null) { + content(state.displayName, isSelected, {}, null) + } else { + ExploreKindItem( + kind = kind, + isClickable = false, + onClick = {}, + modifier = modifier, + backgroundColor = backgroundColor, + isMiuix = isMiuix, + displayText = state.displayName + ) + } } } } + + AppAlertDialog( + data = showFullError, + onDismissRequest = { showFullError = null }, + title = "错误详情", + confirmText = "复制", + onConfirm = { error -> + context.sendToClip(error) + showFullError = null + }, + dismissText = "关闭", + onDismiss = { showFullError = null }, + content = { error -> + SelectionContainer { + AppText( + text = error, + style = LegadoTheme.typography.bodyMedium, + modifier = Modifier + .heightIn(max = 400.dp) + .verticalScroll(rememberScrollState()) + ) + } + } + ) +} + +private val ExploreKind.errorMsg: String? + get() = when { + title.startsWith("ERROR:", ignoreCase = true) -> url ?: title + url?.contains("Exception") == true -> url + title.contains("Exception") -> title + else -> null + } + +private fun Context.findActivity(): AppCompatActivity? { + var context = this + while (context is ContextWrapper) { + if (context is AppCompatActivity) return context + context = context.baseContext + } + return null } @Composable @@ -236,12 +231,7 @@ private fun ToggleTypeItem( state: ExploreKindItemState, valueOverride: String?, onValueChange: ((String) -> Unit)?, - isSelected: Boolean, - modifier: Modifier, - backgroundColor: Color, - isMiuix: Boolean, - trailingIcon: @Composable (() -> Unit)?, - content: (@Composable (displayName: String, isSelected: Boolean, onClick: () -> Unit, trailingIcon: @Composable (() -> Unit)?) -> Unit)? + BaseItem: @Composable (String, () -> Unit) -> Unit ) { val chars = remember(kind.chars) { kind.chars?.filterNotNull().takeUnless { it.isNullOrEmpty() } ?: listOf("chars", "is null") @@ -269,21 +259,7 @@ private fun ToggleTypeItem( state.executeAction(kind.action) } - if (content != null) { - content(text, isSelected, internalOnClick, trailingIcon) - } else { - ExploreKindItem( - kind = kind, - isClickable = true, - onClick = internalOnClick, - modifier = modifier, - backgroundColor = backgroundColor, - isMiuix = isMiuix, - displayText = text, - isSelected = isSelected, - trailingIcon = trailingIcon - ) - } + BaseItem(text, internalOnClick) } @Composable @@ -293,12 +269,7 @@ private fun SelectTypeItem( state: ExploreKindItemState, valueOverride: String?, onValueChange: ((String) -> Unit)?, - isSelected: Boolean, - modifier: Modifier, - backgroundColor: Color, - isMiuix: Boolean, - trailingIcon: @Composable (() -> Unit)?, - content: (@Composable (displayName: String, isSelected: Boolean, onClick: () -> Unit, trailingIcon: @Composable (() -> Unit)?) -> Unit)? + BaseItem: @Composable (String, () -> Unit) -> Unit ) { val chars = remember(kind.chars) { kind.chars?.filterNotNull().takeUnless { it.isNullOrEmpty() } ?: listOf("chars", "is null") @@ -318,25 +289,11 @@ private fun SelectTypeItem( } var showSelector by remember(sourceUrl, kind.title) { mutableStateOf(false) } - Box(modifier = modifier) { + Box { val internalOnClick = { showSelector = true } val displayText = "${state.displayName} $selected" - if (content != null) { - content(displayText, isSelected, internalOnClick, trailingIcon) - } else { - ExploreKindItem( - kind = kind, - isClickable = chars.isNotEmpty(), - onClick = internalOnClick, - modifier = Modifier.fillMaxWidth(), - backgroundColor = backgroundColor, - isMiuix = isMiuix, - displayText = displayText, - isSelected = isSelected, - trailingIcon = trailingIcon - ) - } + BaseItem(displayText, internalOnClick) RoundDropdownMenu( expanded = showSelector,