一些优化

This commit is contained in:
HapeLee
2026-05-17 20:46:28 +08:00
parent da77e67286
commit f4ca57f972
39 changed files with 138 additions and 1010 deletions
@@ -45,6 +45,7 @@ import io.legado.app.domain.usecase.CacheBookChaptersUseCase
import io.legado.app.domain.usecase.ChangeBookSourceUseCase
import io.legado.app.domain.usecase.ClearBookCacheUseCase
import io.legado.app.domain.usecase.DeleteBooksUseCase
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.domain.usecase.GetReadingProgressUseCase
import io.legado.app.domain.usecase.RemoveBookGroupAssignmentUseCase
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
@@ -103,7 +104,6 @@ import io.legado.app.ui.rss.favorites.RssFavoritesViewModel
import io.legado.app.ui.rss.read.ReadRssViewModel
import io.legado.app.ui.rss.source.manage.RssSourceViewModel
import io.legado.app.ui.rss.subscription.RuleSubViewModel
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import org.koin.core.module.dsl.singleOf
import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.explore
package io.legado.app.domain.usecase
import androidx.appcompat.app.AppCompatActivity
import com.script.rhino.runScriptWithContext
@@ -10,7 +10,7 @@ import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.help.source.getExploreInfoMap
import io.legado.app.ui.login.SourceLoginJsExtensions
import io.legado.app.utils.InfoMap
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.ConcurrentHashMap
@@ -76,7 +76,7 @@ class ExploreKindUiUseCase(
override fun reUiView() = onRefreshKinds()
}
)
withContext(IO) {
withContext(Dispatchers.IO) {
evalButtonClick(actionText, source, effectiveInfoMap, title, sourceJsExtensions)
}
}
@@ -92,7 +92,7 @@ class ExploreKindUiUseCase(
private suspend fun getOrLoadBookSource(sourceUrl: String): BookSource? {
sourceCache[sourceUrl]?.let { return it }
return withContext(IO) {
return withContext(Dispatchers.IO) {
bookSourceDao.getBookSource(sourceUrl)
}?.also { source ->
sourceCache[sourceUrl] = source
@@ -127,4 +127,4 @@ class ExploreKindUiUseCase(
AppLog.put("ExploreUI Button $name JavaScript error", e)
}
}
}
}
@@ -61,6 +61,7 @@ import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import io.legado.app.data.entities.SearchBook
import io.legado.app.domain.model.BookShelfState
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
@@ -74,7 +75,6 @@ import io.legado.app.ui.widget.components.book.SearchBookListItem
import io.legado.app.ui.widget.components.button.AnimatedTextButton
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.explore.ExploreKindMultiTypeItem
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import io.legado.app.ui.widget.components.explore.calculateExploreKindRows
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -36,7 +36,7 @@ import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
@@ -91,9 +91,9 @@ import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.log.AppLogSheet
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -77,7 +77,7 @@ import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.checkBox.AppCheckbox
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -50,7 +50,7 @@ import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.image.cover.buildCoverImageRequest
import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.ImageSaveUtils
@@ -60,7 +60,7 @@ import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
@@ -88,10 +88,10 @@ import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.list.ListUiState
import io.legado.app.ui.widget.components.log.AppLogSheet
@@ -56,8 +56,8 @@ import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.heatmap.HeatmapMode
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
@@ -66,8 +66,8 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.theme.fadingEdge
import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.CollapsibleHeader
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.alert.AppAlertDialog
@@ -75,7 +75,6 @@ import io.legado.app.ui.widget.components.button.AppIconButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.heatmap.HEATMAP_CALENDAR_TITLE
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarEndAction
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarStartAction
@@ -89,6 +88,7 @@ import io.legado.app.ui.widget.components.heatmap.rememberDateRange
import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange
import io.legado.app.ui.widget.components.heatmap.rememberWeeks
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.swipe.SwipeAction
@@ -2,8 +2,20 @@ package io.legado.app.ui.book.readRecord.component
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.buildAnnotatedString
@@ -16,7 +28,7 @@ import io.legado.app.ui.book.readRecord.ReadRecordFormatter
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
import io.legado.app.ui.book.readRecord.TimelineItem
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.StringUtils.formatFriendlyDate
@@ -1,10 +1,21 @@
package io.legado.app.ui.book.readRecord.component
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import io.legado.app.ui.book.readRecord.ReadRecordFormatter
import io.legado.app.ui.book.readRecord.ReadRecordUiState
@@ -12,7 +23,7 @@ import io.legado.app.ui.book.readRecord.ReadRecordViewModel
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.text.AppText
import java.time.format.DateTimeFormatter
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -23,17 +22,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Book
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Layers
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material3.ExperimentalMaterial3Api
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.M3GlassScrollBehavior
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -42,9 +36,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -58,6 +52,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.entities.SearchKeyword
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.main.bookshelf.BookShelfItem
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
@@ -68,22 +63,23 @@ import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.book.SearchBookListItem
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.SmallTextButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
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.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.M3GlassScrollBehavior
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarAnimatedActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.book.search.ScopeSelectSheet
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
@@ -480,19 +476,17 @@ fun SearchScreen(
viewModel.onIntent(SearchIntent.DismissEmptyScopeAction)
},
title = stringResource(R.string.draw),
textProvider = {
if (wasPrecisionSearch) {
stringResource(R.string.search_empty_scope_disable_precision, scopeDisplay)
} else {
stringResource(R.string.search_empty_scope_switch_all, scopeDisplay)
}
},
confirmText = stringResource(R.string.ok),
onConfirm = { viewModel.onIntent(SearchIntent.ConfirmEmptyScopeAction) },
dismissText = stringResource(R.string.cancel),
onDismiss = { viewModel.onIntent(SearchIntent.DismissEmptyScopeAction) },
content = {
Text(
text = if (it.wasPrecisionSearch) {
stringResource(R.string.search_empty_scope_disable_precision, it.scopeDisplay)
} else {
stringResource(R.string.search_empty_scope_switch_all, it.scopeDisplay)
}
)
}
)
ScopeSelectSheet(
@@ -681,12 +675,8 @@ private fun SearchResultFooter(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Box(
modifier = Modifier.size(18.dp),
contentAlignment = Alignment.Center
) {
AppCircularProgressIndicator()
}
AppCircularProgressIndicator()
Spacer(modifier = Modifier.padding(horizontal = 8.dp))
AppText(text = stringResource(R.string.is_loading))
}
}
@@ -84,10 +84,10 @@ import io.legado.app.ui.replace.ReplaceEditRoute
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AppFloatingActionButtonMenu
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.CollapsibleHeader
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.FabMenuItem
import io.legado.app.ui.widget.components.SelectionBottomBar
@@ -1,328 +0,0 @@
package io.legado.app.ui.book.toc.rule
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.constant.AppLog
import io.legado.app.data.appDb
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogTocRegexBinding
import io.legado.app.databinding.ItemTocRegexBinding
import io.legado.app.lib.dialogs.alert
//import io.legado.app.lib.theme.backgroundColor
//import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.association.ImportTxtTocRuleDialog
import io.legado.app.ui.file.HandleFileContract
import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.ACache
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.launch
import io.legado.app.utils.readText
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.showHelp
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
/**
* txt目录规则
*/
class TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex),
Toolbar.OnMenuItemClickListener,
TxtTocRuleEditDialog.Callback {
constructor(tocRegex: String?) : this() {
arguments = Bundle().apply {
putString("tocRegex", tocRegex)
}
}
private val importTocRuleKey = "tocRuleUrl"
private val viewModel: TxtTocRuleViewModel by viewModels()
private val binding by viewBinding(DialogTocRegexBinding::bind)
private val adapter by lazy { TocRegexAdapter(requireContext()) }
var selectedName: String? = null
private var durRegex: String? = null
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
showDialogFragment(ImportTxtTocRuleDialog(it))
}
private val importDoc = registerForActivityResult(HandleFileContract()) {
kotlin.runCatching {
it.uri?.readText(requireContext())?.let {
showDialogFragment(ImportTxtTocRuleDialog(it))
}
}.onFailure {
toastOnUi("readTextError:${it.localizedMessage}")
}
}
override fun onStart() {
super.onStart()
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
//binding.toolBar.setBackgroundColor(primaryColor)
durRegex = arguments?.getString("tocRegex")
binding.toolBar.setTitle(R.string.txt_toc_rule)
binding.toolBar.inflateMenu(R.menu.txt_toc_rule)
//binding.toolBar.menu.applyTint(requireContext())
binding.toolBar.setOnMenuItemClickListener(this)
initView()
initData()
}
private fun initView() = binding.run {
recyclerView.addItemDecoration(VerticalDivider(requireContext()))
recyclerView.adapter = adapter
val itemTouchCallback = ItemTouchCallback(adapter)
itemTouchCallback.isCanDrag = true
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recyclerView)
tvCancel.setOnClickListener {
dismissAllowingStateLoss()
}
tvOk.setOnClickListener {
adapter.getItems().forEach { tocRule ->
if (selectedName == tocRule.name) {
val callBack = activity as? CallBack
callBack?.onTocRegexDialogResult(tocRule.rule)
dismissAllowingStateLoss()
return@setOnClickListener
}
}
}
}
private fun initData() {
lifecycleScope.launch {
appDb.txtTocRuleDao.observeAll().catch {
AppLog.put("TXT目录规则对话框获取数据失败\n${it.localizedMessage}", it)
}.flowOn(IO).conflate().collect { tocRules ->
initSelectedName(tocRules)
adapter.setItems(tocRules, adapter.diffItemCallBack)
}
}
}
private fun initSelectedName(tocRules: List<TxtTocRule>) {
if (selectedName == null && durRegex != null) {
tocRules.forEach {
if (durRegex == it.rule) {
selectedName = it.name
return@forEach
}
}
if (selectedName == null) {
selectedName = ""
}
}
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_add -> showDialogFragment(TxtTocRuleEditDialog())
R.id.menu_import_local -> importDoc.launch {
mode = HandleFileContract.FILE
allowExtensions = arrayOf("txt", "json")
}
R.id.menu_import_onLine -> showImportDialog()
R.id.menu_import_qr -> qrCodeResult.launch()
R.id.menu_help -> showHelp("txtTocRuleHelp")
}
return false
}
override fun saveTxtTocRule(txtTocRule: TxtTocRule) {
//viewModel.update(txtTocRule)
}
@SuppressLint("InflateParams")
private fun showImportDialog() {
val aCache = ACache.get(cacheDir = false)
val defaultUrl = "https://gitee.com/fisher52/YueDuJson/raw/master/myTxtChapterRule.json"
val cacheUrls: MutableList<String> = aCache
.getAsString(importTocRuleKey)
?.splitNotBlank(",")
?.toMutableList()
?: mutableListOf()
if (!cacheUrls.contains(defaultUrl)) {
cacheUrls.add(0, defaultUrl)
}
requireContext().alert(titleResource = R.string.import_on_line) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
editView.hint = "url"
editView.setFilterValues(cacheUrls)
editView.delCallBack = {
cacheUrls.remove(it)
aCache.put(importTocRuleKey, cacheUrls.joinToString(","))
}
}
customView { alertBinding.root }
okButton {
val text = alertBinding.editView.text?.toString()
text?.let {
if (it.isAbsUrl() && !cacheUrls.contains(it)) {
cacheUrls.add(0, it)
aCache.put(importTocRuleKey, cacheUrls.joinToString(","))
}
showDialogFragment(ImportTxtTocRuleDialog(it))
}
}
cancelButton()
}
}
inner class TocRegexAdapter(context: Context) :
RecyclerAdapter<TxtTocRule, ItemTocRegexBinding>(context),
ItemTouchCallback.Callback {
val diffItemCallBack = object : DiffUtil.ItemCallback<TxtTocRule>() {
override fun areItemsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean {
if (oldItem.name != newItem.name) {
return false
}
if (oldItem.enable != newItem.enable) {
return false
}
if (oldItem.example != newItem.example) {
return false
}
return true
}
override fun getChangePayload(oldItem: TxtTocRule, newItem: TxtTocRule): Any? {
val payload = Bundle()
if (oldItem.name != newItem.name) {
payload.putBoolean("upName", true)
}
if (oldItem.enable != newItem.enable) {
payload.putBoolean("enabled", newItem.enable)
}
if (oldItem.example != newItem.example) {
payload.putBoolean("upExample", true)
}
if (payload.isEmpty) {
return null
}
return payload
}
}
override fun getViewBinding(parent: ViewGroup): ItemTocRegexBinding {
return ItemTocRegexBinding.inflate(inflater, parent, false)
}
override fun convert(
holder: ItemViewHolder,
binding: ItemTocRegexBinding,
item: TxtTocRule,
payloads: MutableList<Any>
) {
binding.apply {
if (payloads.isEmpty()) {
//root.setBackgroundColor(context.backgroundColor)
rbRegexName.text = item.name
titleExample.text = item.example
rbRegexName.isChecked = item.name == selectedName
swtEnabled.isChecked = item.enable
} else {
for (i in payloads.indices) {
val bundle = payloads[i] as Bundle
bundle.keySet().forEach {
when (it) {
"upName" -> rbRegexName.text = item.name
"upExample" -> titleExample.text = item.example
"enabled" -> swtEnabled.isChecked = item.enable
"upSelect" -> rbRegexName.isChecked = item.name == selectedName
}
}
}
}
}
}
override fun registerListener(holder: ItemViewHolder, binding: ItemTocRegexBinding) {
binding.apply {
rbRegexName.setOnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed && isChecked) {
selectedName = getItem(holder.layoutPosition)?.name
updateItems(0, itemCount - 1, bundleOf("upSelect" to null))
}
}
swtEnabled.setOnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) {
getItem(holder.layoutPosition)?.let {
it.enable = isChecked
//viewModel.update(it)
}
}
}
ivEdit.setOnClickListener {
showDialogFragment(TxtTocRuleEditDialog(getItem(holder.layoutPosition)?.id))
}
ivDelete.setOnClickListener {
getItem(holder.layoutPosition)?.let { item ->
alert(R.string.draw) {
setMessage(getString(R.string.sure_del) + "\n" + item.name)
noButton()
yesButton {
//viewModel.delete(item)
}
}
}
}
}
}
private var isMoved = false
override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
swapItem(srcPosition, targetPosition)
isMoved = true
return super.swap(srcPosition, targetPosition)
}
override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.onClearView(recyclerView, viewHolder)
if (isMoved) {
for ((index, item) in getItems().withIndex()) {
item.serialNumber = index + 1
}
//viewModel.update(*getItems().toTypedArray())
}
isMoved = false
}
}
interface CallBack {
fun onTocRegexDialogResult(tocRegex: String) {}
}
}
@@ -1,147 +0,0 @@
package io.legado.app.ui.book.toc.rule
import android.app.Application
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.viewModels
import io.legado.app.R
import io.legado.app.base.BaseBottomSheetDialogFragment
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppLog
import io.legado.app.data.appDb
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.databinding.DialogTocRegexEditBinding
import io.legado.app.exception.NoStackTraceException
//import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers
import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException
class TxtTocRuleEditDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_toc_regex_edit),
Toolbar.OnMenuItemClickListener {
constructor(id: Long?) : this() {
id ?: return
arguments = Bundle().apply {
putLong("id", id)
}
}
private val binding by viewBinding(DialogTocRegexEditBinding::bind)
private val viewModel by viewModels<ViewModel>()
private val callback get() = (parentFragment as? Callback) ?: activity as? Callback
override fun onStart() {
super.onStart()
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
//binding.toolBar.setBackgroundColor(primaryColor)
initMenu()
viewModel.initData(arguments?.getLong("id")) {
upRuleView(it)
}
}
private fun initMenu() {
binding.toolBar.inflateMenu(R.menu.txt_toc_rule_edit)
//binding.toolBar.menu.applyTint(requireContext())
binding.toolBar.setOnMenuItemClickListener(this)
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_save -> {
val tocRule = getRuleFromView()
if (checkValid(tocRule)) {
callback?.saveTxtTocRule(getRuleFromView())
dismissAllowingStateLoss()
}
}
R.id.menu_copy_rule -> context?.sendToClip(GSON.toJson(getRuleFromView()))
R.id.menu_paste_rule -> viewModel.pasteRule {
upRuleView(it)
}
}
return true
}
private fun checkValid(tocRule: TxtTocRule): Boolean {
if (tocRule.name.isEmpty()) {
toastOnUi("名称不能为空")
return false
}
try {
Pattern.compile(tocRule.rule, Pattern.MULTILINE)
} catch (ex: PatternSyntaxException) {
AppLog.put("正则语法错误或不支持(txt)${ex.localizedMessage}", ex, true)
return false
}
return true
}
private fun upRuleView(tocRule: TxtTocRule?) {
binding.tvRuleName.setText(tocRule?.name)
binding.tvRuleRegex.setText(tocRule?.rule)
binding.tvRuleExample.setText(tocRule?.example)
}
private fun getRuleFromView(): TxtTocRule {
val tocRule = viewModel.tocRule ?: TxtTocRule().apply {
viewModel.tocRule = this
}
binding.run {
tocRule.name = tvRuleName.text.toString()
tocRule.rule = tvRuleRegex.text.toString()
tocRule.example = tvRuleExample.text.toString()
}
return tocRule
}
class ViewModel(application: Application) : BaseViewModel(application) {
var tocRule: TxtTocRule? = null
fun initData(id: Long?, finally: (tocRule: TxtTocRule?) -> Unit) {
if (tocRule != null) return
execute {
if (id == null) return@execute
tocRule = appDb.txtTocRuleDao.get(id)
}.onFinally {
finally.invoke(tocRule)
}
}
fun pasteRule(success: (TxtTocRule) -> Unit) {
execute(context = Dispatchers.Main) {
val text = context.getClipText()
if (text.isNullOrBlank()) {
throw NoStackTraceException("剪贴板为空")
}
GSON.fromJsonObject<TxtTocRule>(text).getOrNull()
?: throw NoStackTraceException("格式不对")
}.onSuccess {
success.invoke(it)
}.onError {
context.toastOnUi(it.localizedMessage ?: "Error")
it.printOnDebug()
}
}
}
interface Callback {
fun saveTxtTocRule(txtTocRule: TxtTocRule)
}
}
@@ -1,66 +0,0 @@
package io.legado.app.ui.config
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.lifecycleScope
import io.legado.app.R
import io.legado.app.base.BaseBottomSheetDialogFragment
import io.legado.app.databinding.DialogCoverRuleConfigBinding
//import io.legado.app.lib.theme.primaryColor
import io.legado.app.model.BookCover
import io.legado.app.utils.GSON
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import splitties.views.onClick
class CoverRuleConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_cover_rule_config) {
val binding by viewBinding(DialogCoverRuleConfigBinding::bind)
override fun onStart() {
super.onStart()
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
//binding.toolBar.setBackgroundColor(primaryColor)
initData()
binding.tvCancel.onClick {
dismissAllowingStateLoss()
}
binding.tvOk.onClick {
val enable = binding.cbEnable.isChecked
val searchUrl = binding.editSearchUrl.text?.toString()
val coverRule = binding.editCoverUrlRule.text?.toString()
if (searchUrl.isNullOrBlank() || coverRule.isNullOrBlank()) {
toastOnUi("搜索url和cover规则不能为空")
} else {
BookCover.CoverRule(enable, searchUrl, coverRule).let { config ->
BookCover.saveCoverRule(config)
}
dismissAllowingStateLoss()
}
}
binding.tvFooterLeft.onClick {
BookCover.delCoverRule()
dismissAllowingStateLoss()
}
}
private fun initData() {
lifecycleScope.launch {
val rule = withContext(IO) {
BookCover.getCoverRule()
}
Log.e("coverRule", GSON.toJson(rule))
binding.cbEnable.isChecked = rule.enable
binding.editSearchUrl.setText(rule.searchUrl)
binding.editCoverUrlRule.setText(rule.coverRule)
}
}
}
@@ -1,128 +0,0 @@
package io.legado.app.ui.config
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.Toolbar
import io.legado.app.R
import io.legado.app.base.BaseBottomSheetDialogFragment
import io.legado.app.databinding.DialogDirectLinkUploadConfigBinding
import io.legado.app.help.DirectLinkUpload
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector
//import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.GSON
import io.legado.app.utils.applyTint
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getClipText
import io.legado.app.utils.sendToClip
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import splitties.init.appCtx
import splitties.views.onClick
class DirectLinkUploadConfig : BaseBottomSheetDialogFragment(R.layout.dialog_direct_link_upload_config),
Toolbar.OnMenuItemClickListener {
private val binding by viewBinding(DialogDirectLinkUploadConfigBinding::bind)
override fun onStart() {
super.onStart()
//setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
//binding.toolBar.setBackgroundColor(primaryColor)
binding.toolBar.inflateMenu(R.menu.direct_link_upload_config)
binding.toolBar.menu.applyTint(requireContext())
binding.toolBar.setOnMenuItemClickListener(this)
upView(DirectLinkUpload.getRule())
binding.tvCancel.onClick {
dismiss()
}
binding.tvFooterLeft.onClick {
test()
}
binding.tvOk.onClick {
getRule()?.let { rule ->
DirectLinkUpload.putConfig(rule)
dismiss()
}
}
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_import_default -> importDefault()
R.id.menu_copy_rule -> getRule()?.let { rule ->
requireContext().sendToClip(GSON.toJson(rule))
}
R.id.menu_paste_rule -> runCatching {
requireContext().getClipText()!!.let {
val rule = GSON.fromJsonObject<DirectLinkUpload.Rule>(it).getOrThrow()
upView(rule)
}
}.onFailure {
toastOnUi("剪贴板为空或格式不对")
}
}
return true
}
private fun upView(rule: DirectLinkUpload.Rule) {
binding.editUploadUrl.setText(rule.uploadUrl)
binding.editDownloadUrlRule.setText(rule.downloadUrlRule)
binding.editSummary.setText(rule.summary)
binding.cbCompress.isChecked = rule.compress
}
private fun getRule(): DirectLinkUpload.Rule? {
val uploadUrl = binding.editUploadUrl.text?.toString()
val downloadUrlRule = binding.editDownloadUrlRule.text?.toString()
val summary = binding.editSummary.text?.toString()
val compress = binding.cbCompress.isChecked
if (uploadUrl.isNullOrBlank()) {
toastOnUi("上传Url不能为空")
return null
}
if (downloadUrlRule.isNullOrBlank()) {
toastOnUi("下载Url规则不能为空")
return null
}
if (summary.isNullOrBlank()) {
toastOnUi("注释不能为空")
return null
}
return DirectLinkUpload.Rule(uploadUrl, downloadUrlRule, summary, compress)
}
private fun importDefault() {
requireContext().selector(DirectLinkUpload.defaultRules) { _, rule, _ ->
upView(rule)
}
}
private fun test() {
val rule = getRule() ?: return
execute {
DirectLinkUpload.upLoad("test.json", "{}", "application/json", rule)
}.onError {
alertTestResult(it.localizedMessage ?: "ERROR")
}.onSuccess { result ->
alertTestResult(result)
}
}
private fun alertTestResult(result: String) {
alert {
setTitle("result")
setMessage(result)
okButton()
negativeButton(R.string.copy_text) {
appCtx.sendToClip(result)
}
}
}
}
@@ -2,105 +2,56 @@ package io.legado.app.ui.config.coverConfig
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.SettingsBackupRestore
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.TextButton
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.help.DefaultData
import io.legado.app.model.BookCover
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import splitties.init.appCtx
private const val DEFAULT_SEARCH_URL = "data:;base64,{{java.base64Encode(key)}},{\"type\":\"lyc\"}"
private val DEFAULT_COVER_RULE = """
@js:
var key = java.hexDecodeToString(result);
var url1 = `https://pre-api.tuishujun.com/api/searchBook?search_value=${'$'}{key}&page=1&pageSize=20`;
var url2 = `http://m.ypshuo.com/api/novel/search?keyword=${'$'}{key}&searchType=1&page=1`;
var [rr1, rr2] = java.ajaxAll([url1, url2]).map(r => r.body());
function jjson(str, rule) {
try {
return com.jayway.jsonpath.JsonPath.read(str, rule);
} catch (e) {
return [];
}
}
rr1 = jjson(rr1, '$.data.data[*]');
rr2 = jjson(rr2, '$.data.data[*]');
var na = String(book.name),
au = String(book.author);
function search() {
for (let char of rr1) {
//本地书名包含搜索结果书名
if (na.includes(char.title + '')) {
let au2 = char.author_nickname + '';
//作者匹配
if (au.includes(au2) || au2.includes(au)) {
return char.cover;
}
}
}
for (let char of rr2) {
if (na.includes(char.novel_name + '')) {
let au2 = char.author_name + '';
if (au.includes(au2) || au2.includes(au)) {
return char.novel_img;
}
}
}
return '';
}
search()
""".trimIndent()
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CoverRuleConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit
) {
var ruleState by remember { mutableStateOf<BookCover.CoverRule?>(null) }
var enable by remember { mutableStateOf(false) }
var searchUrl by remember { mutableStateOf("") }
var coverRule by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
val rule = withContext(Dispatchers.IO) {
BookCover.getCoverRule()
LaunchedEffect(show) {
if (show) {
val rule = withContext(Dispatchers.IO) {
BookCover.getCoverRule()
}
ruleState = rule
enable = rule.enable
searchUrl = rule.searchUrl
coverRule = rule.coverRule
}
enable = rule.enable
searchUrl = rule.searchUrl
coverRule = rule.coverRule
}
AppModalBottomSheet(
@@ -111,8 +62,11 @@ fun CoverRuleConfigSheet(
MediumIconButton(
imageVector = Icons.Default.SettingsBackupRestore,
onClick = {
searchUrl = DEFAULT_SEARCH_URL
coverRule = DEFAULT_COVER_RULE
DefaultData.coverRule.let {
enable = it.enable
searchUrl = it.searchUrl
coverRule = it.coverRule
}
appCtx.toastOnUi(R.string.restore_default)
}
)
@@ -124,8 +78,16 @@ fun CoverRuleConfigSheet(
if (searchUrl.isBlank() || coverRule.isBlank()) {
appCtx.toastOnUi("搜索url和cover规则不能为空")
} else {
BookCover.CoverRule(enable, searchUrl, coverRule).let { config ->
BookCover.saveCoverRule(config)
val newConfig = ruleState?.copy(
enable = enable,
searchUrl = searchUrl,
coverRule = coverRule
) ?: BookCover.CoverRule(enable, searchUrl, coverRule)
if (newConfig == DefaultData.coverRule) {
BookCover.delCoverRule()
} else {
BookCover.saveCoverRule(newConfig)
}
onDismissRequest()
}
@@ -136,6 +98,7 @@ fun CoverRuleConfigSheet(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
@@ -50,9 +50,9 @@ import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.BookshelfCover
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.image.cover.BookshelfCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toTimeAgo
@@ -214,7 +214,7 @@ fun BookshelfItem(
Column(
modifier = Modifier
.weight(1f)
.padding(top = 4.dp, bottom = 4.dp, end = 4.dp),
.padding(top = 4.dp, bottom = 4.dp, end = 8.dp, start = 4.dp),
verticalArrangement = Arrangement.Center
) {
Row(
@@ -232,7 +232,7 @@ fun BookshelfItem(
modifier = Modifier.weight(1f)
)
titleEnd?.let {
Box(modifier = Modifier.padding(top = 4.dp)) {
Box(modifier = Modifier.padding(top = 4.dp, start = 4.dp)) {
it.invoke()
}
}
@@ -523,7 +523,7 @@ fun BookGroupItemHorizontalCovers(
) {
Column(
modifier = Modifier
.padding(vertical = 4.dp),
.padding(vertical = 8.dp, horizontal = 4.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
@@ -29,7 +29,6 @@ import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.VerticalAlignTop
import androidx.compose.material3.ExperimentalMaterial3Api
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
@@ -41,8 +40,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
@@ -53,23 +52,22 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.help.source.getExploreInfoMap
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.book.search.SearchScope
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.explore.ExploreKindMultiTypeItem
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
@@ -79,7 +77,6 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.progressIndicator.AppContainedLoadingIndicator
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.startActivity
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
@@ -6,10 +6,10 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.data.repository.ExploreRepository
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.help.source.clearExploreKindsCache
import io.legado.app.help.source.exploreKinds
import io.legado.app.help.source.getExploreInfoMap
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import io.legado.app.ui.widget.components.explore.calculateExploreKindRows
import io.legado.app.ui.widget.components.list.ListUiState
import kotlinx.collections.immutable.ImmutableList
@@ -52,12 +52,12 @@ import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.SourceIcon
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.image.sourceIcon.SourceIcon
import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
@@ -61,7 +61,7 @@ import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.image.cover.buildCoverImageRequest
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.catch
@@ -33,11 +33,11 @@ import io.legado.app.data.entities.RssStar
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SourceIcon
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.image.sourceIcon.SourceIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold
import org.koin.androidx.compose.koinViewModel
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget
package io.legado.app.ui.widget.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Column
@@ -10,7 +10,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -1,40 +0,0 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SectionHeader(
// 左侧主标题内容
titleContent: @Composable () -> Unit,
// 右侧或副标题内容
detailContent: @Composable (() -> Unit)? = null,
// 主要内容的排列方式
verticalArrangement: Arrangement.Vertical = Arrangement.Center,
// 定义水平对齐方式
horizontalArrangement: Arrangement.Horizontal = Arrangement.SpaceBetween,
// 根容器的 Padding
contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = horizontalArrangement
) {
titleContent()
detailContent?.let {
it()
}
}
}
@@ -144,6 +144,7 @@ fun <T> AppAlertDialog(
onDismissRequest: () -> Unit,
title: String? = null,
text: String? = null,
textProvider: @Composable (T.() -> String)? = null,
confirmText: String = "确定",
onConfirm: ((T) -> Unit)? = null,
dismissText: String = "取消",
@@ -159,11 +160,17 @@ fun <T> AppAlertDialog(
val currentData = cachedData
if (currentData != null) {
val currentText = text ?: textProvider?.invoke(currentData)
var cachedText by remember { mutableStateOf(currentText) }
if (currentText != null) {
cachedText = currentText
}
AppAlertDialog(
show = data != null,
onDismissRequest = onDismissRequest,
title = title,
text = text,
text = currentText ?: cachedText,
modifier = modifier,
confirmText = confirmText,
onConfirm = onConfirm?.let { { it(currentData) } },
@@ -33,7 +33,7 @@ import io.legado.app.data.entities.SearchBook
import io.legado.app.domain.model.BookShelfState
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalSharedTransitionApi::class)
@@ -1,97 +0,0 @@
package io.legado.app.ui.widget.components.cover
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Book
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.ImageLoader
import coil.compose.AsyncImage
import io.legado.app.ui.theme.LegadoTheme
import org.koin.compose.koinInject
@Composable
fun Cover(
path: Any?,
modifier: Modifier = Modifier.width(48.dp),
badgeContent: (@Composable RowScope.() -> Unit)? = null,
loadOnlyWifi: Boolean = false,
sourceOrigin: String? = null,
imageLoader: ImageLoader = koinInject(),
onLoadFinish: (() -> Unit)? = null
) {
val context = LocalContext.current
val boxModifier = modifier
.aspectRatio(3f / 4f)
.clip(RoundedCornerShape(4.dp))
.background(LegadoTheme.colorScheme.surfaceContainer)
Box(modifier = boxModifier) {
if (path == null) {
Icon(
Icons.Default.Book,
contentDescription = null,
tint = LegadoTheme.colorScheme.surfaceContainerHighest,
modifier = Modifier
.size(24.dp)
.align(Alignment.Center)
)
} else {
AsyncImage(
model = buildCoverImageRequest(
context = context,
data = path,
sourceOrigin = sourceOrigin,
loadOnlyWifi = loadOnlyWifi,
crossfade = true,
) {
listener(
onSuccess = { _, _ -> onLoadFinish?.invoke() },
onError = { _, _ -> onLoadFinish?.invoke() }
)
},
imageLoader = imageLoader,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
if (badgeContent != null) {
Surface(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(2.dp),
shape = RoundedCornerShape(4.dp),
color = LegadoTheme.colorScheme.secondaryContainer,
contentColor = LegadoTheme.colorScheme.onSecondaryContainer,
tonalElevation = 2.dp
) {
Row(
modifier = Modifier.padding(2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = badgeContent
)
}
}
}
}
@@ -10,13 +10,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowRight
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.UnfoldMore
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -25,32 +21,28 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import io.legado.app.App
import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.help.source.getExploreInfoMap
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
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.components.text.AppText
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.utils.showDialogFragment
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.basic.ArrowUpDown
@Composable
fun ExploreKindMultiTypeItem(
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.cover
package io.legado.app.ui.widget.components.image.cover
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
@@ -14,8 +14,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.cover
package io.legado.app.ui.widget.components.image.cover
import android.graphics.Paint
import android.graphics.Typeface
@@ -15,7 +15,6 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -122,7 +121,9 @@ fun CoilBookCover(
contentDescription = null,
imageLoader = koinInject(),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(4.dp))
)
}
@@ -138,7 +139,9 @@ fun CoilBookCover(
contentDescription = null,
imageLoader = koinInject(),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(4.dp)),
onSuccess = {
isOnlineCoverLoaded = true
onLoadFinish?.invoke()
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.cover
package io.legado.app.ui.widget.components.image.cover
import android.content.Context
import coil.request.ImageRequest
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components
package io.legado.app.ui.widget.components.image.sourceIcon
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -87,6 +87,7 @@ private fun AppTab(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = LegadoTheme.typography.labelLargeEmphasized,
modifier = Modifier.padding(horizontal = 8.dp),
color = if (selected) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.onSurfaceVariant
)