一些神必优化

This commit is contained in:
HapeLee committed 2026-05-27 01:00:58 +08:00
1 parent df2aaff0e2
commit 30fe70ffae
89 files changed
+2242 -1525

No files matched your search

@@ -4,10 +4,13 @@ import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
val Context.localDataStore: DataStore<Preferences> by preferencesDataStore(name = "local_ui_status") val Context.localDataStore: DataStore<Preferences> by preferencesDataStore(name = "local_ui_status")
object LocalPreferencesKeys { object LocalPreferencesKeys {
val SHOW_THEME_REFACTOR_TIP = booleanPreferencesKey("show_theme_refactor_tip") val SHOW_THEME_REFACTOR_TIP = booleanPreferencesKey("show_theme_refactor_tip")
val SEARCH_LAYOUT_MODE = intPreferencesKey("search_layout_mode")
val MATCH_MODE = intPreferencesKey("match_mode")
} }
@@ -80,9 +80,10 @@ class ExploreRepositoryImpl(
override suspend fun exploreBooks( override suspend fun exploreBooks(
bookSource: BookSource, bookSource: BookSource,
url: String, url: String,
page: Int page: Int,
key: String?
): List<SearchBook> { ): List<SearchBook> {
return WebBook.exploreBookSuspend(bookSource, url, page) return WebBook.exploreBookSuspend(bookSource, url, page, key = key, isSearch = key != null)
} }
override suspend fun getSourceExploreKinds(sourceUrl: String): List<ExploreKind> = withContext(IO) { override suspend fun getSourceExploreKinds(sourceUrl: String): List<ExploreKind> = withContext(IO) {
@@ -5,5 +5,10 @@ import io.legado.app.data.entities.SearchBook
interface ExploreBooksGateway { interface ExploreBooksGateway {
suspend fun getBookSource(sourceUrl: String): BookSource? suspend fun getBookSource(sourceUrl: String): BookSource?
suspend fun exploreBooks(bookSource: BookSource, url: String, page: Int): List<SearchBook> suspend fun exploreBooks(
bookSource: BookSource,
url: String,
page: Int,
key: String? = null
): List<SearchBook>
} }
@@ -0,0 +1,10 @@
package io.legado.app.domain.model
enum class MatchMode(val value: Int) {
DEFAULT(0),
EXACT(1);
companion object {
fun of(value: Int) = entries.getOrElse(value) { DEFAULT }
}
}
@@ -20,14 +20,17 @@ class ExploreBooksUseCase(
sourceUrl: String, sourceUrl: String,
moduleUrl: String?, moduleUrl: String?,
args: String?, args: String?,
page: Int = 1 page: Int = 1,
key: String? = null,
): ExploreResult = withContext(Dispatchers.IO) { ): ExploreResult = withContext(Dispatchers.IO) {
val base = gateway.getBookSource(sourceUrl) val base = gateway.getBookSource(sourceUrl)
?: throw SourceNotFound(sourceUrl) ?: throw SourceNotFound(sourceUrl)
val source = args?.let { base.copy().also { s -> s.setVariable(it) } } ?: base val source = args?.let { base.copy().also { s -> s.setVariable(it) } } ?: base
val resolvedUrl = moduleUrl ?: source.exploreUrl val resolvedUrl = moduleUrl
?: (if (key != null) source.searchUrl else null)
?: source.exploreUrl
?: throw NoExploreUrl(sourceUrl) ?: throw NoExploreUrl(sourceUrl)
val books = gateway.exploreBooks(source, resolvedUrl, page) val books = gateway.exploreBooks(source, resolvedUrl, page, key = key)
ExploreResult(resolvedUrl, books) ExploreResult(resolvedUrl, books)
} }
@@ -109,9 +109,12 @@ object WebBook {
url: String, url: String,
page: Int? = 1, page: Int? = 1,
ruleData: RuleDataInterface = RuleData(), ruleData: RuleDataInterface = RuleData(),
key: String? = null,
isSearch: Boolean = false,
): ArrayList<SearchBook> { ): ArrayList<SearchBook> {
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
mUrl = url, mUrl = url,
key = key,
page = page, page = page,
baseUrl = bookSource.bookSourceUrl, baseUrl = bookSource.bookSourceUrl,
source = bookSource, source = bookSource,
@@ -137,7 +140,7 @@ object WebBook {
analyzeUrl = analyzeUrl, analyzeUrl = analyzeUrl,
baseUrl = res.url, baseUrl = res.url,
body = res.body, body = res.body,
isSearch = false isSearch = isSearch
) )
} }
@@ -146,8 +149,10 @@ object WebBook {
url: String, url: String,
page: Int? = 1, page: Int? = 1,
ruleData: RuleDataInterface = RuleData(), ruleData: RuleDataInterface = RuleData(),
key: String? = null,
isSearch: Boolean = false,
): List<SearchBook> { ): List<SearchBook> {
return exploreBookAwait(bookSource, url, page, ruleData) return exploreBookAwait(bookSource, url, page, ruleData, key, isSearch)
} }
suspend fun exploreBookWithResolvedUrl( suspend fun exploreBookWithResolvedUrl(
@@ -40,7 +40,7 @@ import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.AppFloatingActionButton import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.NormalCard 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.card.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
@@ -347,7 +347,7 @@ private fun BookCacheBookCard(
color = LegadoTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
) )
if (item.hasDownloadTask || item.cachedCount < item.totalCount) { if (item.hasDownloadTask || item.cachedCount < item.totalCount) {
SmallTonalIconButton( SmallTonalButton(
onClick = { onClick = {
if (item.hasActiveDownload) { if (item.hasActiveDownload) {
onIntent(BookCacheManageIntent.StopBookDownload(item.bookUrl)) onIntent(BookCacheManageIntent.StopBookDownload(item.bookUrl))
@@ -355,7 +355,7 @@ private fun BookCacheBookCard(
onIntent(BookCacheManageIntent.StartBookDownload(item.bookUrl)) onIntent(BookCacheManageIntent.StartBookDownload(item.bookUrl))
} }
}, },
imageVector = if (item.hasActiveDownload) Icons.Default.Stop else Icons.Default.PlayArrow, icon = if (item.hasActiveDownload) Icons.Default.Stop else Icons.Default.PlayArrow,
contentDescription = when { contentDescription = when {
item.hasActiveDownload -> "暂停本书下载" item.hasActiveDownload -> "暂停本书下载"
item.isPaused -> "继续本书下载" item.isPaused -> "继续本书下载"
@@ -363,9 +363,9 @@ private fun BookCacheBookCard(
} }
) )
} }
SmallTonalIconButton( SmallTonalButton(
onClick = { onDeleteBook(item) }, onClick = { onDeleteBook(item) },
imageVector = Icons.Default.Delete, icon = Icons.Default.Delete,
contentDescription = null contentDescription = null
) )
} }
@@ -412,21 +412,21 @@ private fun BookCacheChapterRow(
) )
} }
if (item.isWaiting || item.isDownloading) { if (item.isWaiting || item.isDownloading) {
SmallTonalIconButton( SmallTonalButton(
onClick = onStop, onClick = onStop,
imageVector = Icons.Default.Stop, icon = Icons.Default.Stop,
contentDescription = "暂停章节下载" contentDescription = "暂停章节下载"
) )
} else if (item.isPaused || !item.isCached) { } else if (item.isPaused || !item.isCached) {
SmallTonalIconButton( SmallTonalButton(
onClick = onDownload, onClick = onDownload,
imageVector = Icons.Default.Download, icon = Icons.Default.Download,
contentDescription = if (item.isPaused) "继续章节下载" else "下载章节" contentDescription = if (item.isPaused) "继续章节下载" else "下载章节"
) )
} }
SmallTonalIconButton( SmallTonalButton(
onClick = onDelete, onClick = onDelete,
imageVector = Icons.Default.Delete, icon = Icons.Default.Delete,
contentDescription = null contentDescription = null
) )
} }
@@ -0,0 +1,65 @@
package io.legado.app.ui.book.explore
import androidx.compose.runtime.Stable
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.domain.model.BookShelfState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Stable
data class ExploreShowUiState(
val sourceUrl: String? = null,
val books: ImmutableList<ExploreBookItemUi> = persistentListOf(),
val kinds: ImmutableList<ExploreKind> = persistentListOf(),
val selectedKindTitle: String? = null,
val layoutState: Int = 0,
val gridCount: Int = 3,
val isLoading: Boolean = false,
val isRefreshing: Boolean = false,
val isEnd: Boolean = false,
val errorMsg: String? = null,
val sheet: ExploreShowSheet = ExploreShowSheet.None,
)
@Stable
data class ExploreBookItemUi(
val book: SearchBook,
val shelfState: BookShelfState = BookShelfState.NOT_IN_SHELF,
)
sealed interface ExploreShowSheet {
data object None : ExploreShowSheet
data object KindSelect : ExploreShowSheet
data object GridCount : ExploreShowSheet
}
sealed interface ExploreShowIntent {
data class InitData(
val sourceUrl: String?,
val exploreUrl: String?,
) : ExploreShowIntent
data object LoadMore : ExploreShowIntent
data object Refresh : ExploreShowIntent
data class SwitchKind(val kind: ExploreKind) : ExploreShowIntent
data object ToggleLayout : ExploreShowIntent
data class SaveGridCount(val count: Int) : ExploreShowIntent
data class ShowSheet(val sheet: ExploreShowSheet) : ExploreShowIntent
data object DismissSheet : ExploreShowIntent
data class OpenBook(val book: SearchBook, val sharedCoverKey: String?) : ExploreShowIntent
data class AddToShelf(val book: SearchBook) : ExploreShowIntent
}
sealed interface ExploreShowEffect {
data class OpenBookInfo(
val name: String,
val author: String,
val bookUrl: String,
val origin: String?,
val coverPath: String?,
val sharedCoverKey: String?,
) : ExploreShowEffect
data class ShowMessage(val message: String) : ExploreShowEffect
}
@@ -6,7 +6,6 @@ import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@@ -33,11 +32,8 @@ import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.outlined.FilterAlt import androidx.compose.material.icons.outlined.FilterAlt
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -48,19 +44,19 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.domain.model.BookShelfState import io.legado.app.domain.model.BookShelfState
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.ui.config.coverConfig.CoverConfig import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.main.bookCoverSharedElementKey import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource import io.legado.app.ui.theme.responsiveHazeSource
import io.legado.app.ui.widget.components.AppPullToRefresh import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppSlider
import io.legado.app.ui.widget.components.LoadMoreFooter import io.legado.app.ui.widget.components.LoadMoreFooter
import io.legado.app.ui.widget.components.book.SearchBookGridItem import io.legado.app.ui.widget.components.book.SearchBookGridItem
import io.legado.app.ui.widget.components.book.SearchBookListItem import io.legado.app.ui.widget.components.book.SearchBookListItem
@@ -74,7 +70,17 @@ import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarActionButton import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
private enum class BookFilterState(val id: Int) {
SHOW_ALL(0),
HIDE_IN_SHELF(1),
HIDE_SAME_NAME_AUTHOR(2),
SHOW_NOT_IN_SHELF_ONLY(3);
companion object {
fun fromId(id: Int) = entries.getOrElse(id) { SHOW_ALL }
}
}
@SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight") @SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
@OptIn( @OptIn(
@@ -83,55 +89,51 @@ import org.koin.compose.koinInject
) )
@Composable @Composable
fun ExploreShowScreen( fun ExploreShowScreen(
title: String, viewModel: ExploreShowViewModel = koinViewModel(),
sourceUrl: String?, title: String = "",
exploreUrl: String?,
onBack: () -> Unit, onBack: () -> Unit,
onBookClick: (SearchBook, String?) -> Unit, onBookClick: (SearchBook, String?) -> Unit,
viewModel: ExploreShowViewModel = koinViewModel(),
sharedTransitionScope: SharedTransitionScope? = null, sharedTransitionScope: SharedTransitionScope? = null,
animatedVisibilityScope: AnimatedVisibilityScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null,
) { ) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(viewModel) {
viewModel.effects.collect { effect ->
when (effect) {
is ExploreShowEffect.OpenBookInfo -> onBookClick(
SearchBook(
name = effect.name,
author = effect.author,
bookUrl = effect.bookUrl,
origin = effect.origin ?: "",
coverUrl = effect.coverPath,
),
effect.sharedCoverKey,
)
is ExploreShowEffect.ShowMessage -> {}
}
}
}
var previewBook by remember { mutableStateOf<SearchBook?>(null) } var previewBook by remember { mutableStateOf<SearchBook?>(null) }
var previewSharedCoverKey by remember { mutableStateOf<String?>(null) } var previewSharedCoverKey by remember { mutableStateOf<String?>(null) }
LaunchedEffect(sourceUrl, exploreUrl, viewModel) {
viewModel.initData(sourceUrl, exploreUrl)
}
val rawBooks by viewModel.uiBooks.collectAsState()
val filterStateId = CoverConfig.exploreFilterState val filterStateId = CoverConfig.exploreFilterState
val books = remember(rawBooks, filterStateId) { val books = remember(state.books, filterStateId) {
val filter = BookFilterState.fromId(filterStateId) val filter = BookFilterState.fromId(filterStateId)
when (filter) { when (filter) {
BookFilterState.SHOW_ALL -> rawBooks BookFilterState.SHOW_ALL -> state.books
BookFilterState.HIDE_IN_SHELF -> rawBooks.filter { it.shelfState != BookShelfState.IN_SHELF } BookFilterState.HIDE_IN_SHELF -> state.books.filter { it.shelfState != BookShelfState.IN_SHELF }
BookFilterState.HIDE_SAME_NAME_AUTHOR -> rawBooks.filter { it.shelfState != BookShelfState.SAME_NAME_AUTHOR } BookFilterState.HIDE_SAME_NAME_AUTHOR -> state.books.filter { it.shelfState != BookShelfState.SAME_NAME_AUTHOR }
BookFilterState.SHOW_NOT_IN_SHELF_ONLY -> rawBooks.filter { it.shelfState == BookShelfState.NOT_IN_SHELF } BookFilterState.SHOW_NOT_IN_SHELF_ONLY -> state.books.filter { it.shelfState == BookShelfState.NOT_IN_SHELF }
} }
} }
val isBookEnd by viewModel.isEnd.collectAsState()
val shouldTriggerAutoLoad by viewModel.shouldTriggerAutoLoad.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
val errorMsg by viewModel.errorMsg.collectAsState()
val selectedTitle by viewModel.selectedKindTitle.collectAsState()
val listState = rememberLazyListState() val listState = rememberLazyListState()
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
var showKindSheet by remember { mutableStateOf(false) } val isGridMode = state.layoutState == 1
val layoutState by viewModel.layoutState.collectAsState()
val isGridMode = layoutState == 1
var showGridCountSheet by remember { mutableStateOf(false) }
val gridColumnCount by viewModel.gridCount.collectAsState()
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val exploreKindUseCase: ExploreKindUiUseCase = koinInject()
LaunchedEffect(sourceUrl) {
exploreKindUseCase.warmUp(sourceUrl)
}
val isRefreshing by viewModel.isRefreshing.collectAsState()
val hazeState = remember { HazeState() } val hazeState = remember { HazeState() }
val shouldLoadMoreList = remember { val shouldLoadMoreList = remember {
derivedStateOf { derivedStateOf {
@@ -150,17 +152,11 @@ fun ExploreShowScreen(
} }
LaunchedEffect(shouldLoadMoreList.value, isGridMode) { LaunchedEffect(shouldLoadMoreList.value, isGridMode) {
if (!isGridMode && shouldLoadMoreList.value) viewModel.loadMore() if (!isGridMode && shouldLoadMoreList.value) viewModel.onIntent(ExploreShowIntent.LoadMore)
} }
LaunchedEffect(shouldLoadMoreGrid.value, isGridMode) { LaunchedEffect(shouldLoadMoreGrid.value, isGridMode) {
if (isGridMode && shouldLoadMoreGrid.value) viewModel.loadMore() if (isGridMode && shouldLoadMoreGrid.value) viewModel.onIntent(ExploreShowIntent.LoadMore)
}
LaunchedEffect(shouldTriggerAutoLoad) {
if (shouldTriggerAutoLoad) {
viewModel.loadMore()
}
} }
LaunchedEffect(isGridMode) { LaunchedEffect(isGridMode) {
@@ -175,17 +171,14 @@ fun ExploreShowScreen(
} }
} }
AppModalBottomSheet( AppModalBottomSheet(
show = showGridCountSheet, show = state.sheet == ExploreShowSheet.GridCount,
modifier = Modifier onDismissRequest = { viewModel.onIntent(ExploreShowIntent.DismissSheet) }
.padding(16.dp),
onDismissRequest = { showGridCountSheet = false }
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 16.dp), .padding(bottom = 32.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
@@ -193,49 +186,38 @@ fun ExploreShowScreen(
text = "布局列数", text = "布局列数",
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold) style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
TextCard( TextCard(
text = "$gridColumnCount 列", text = "${state.gridCount} 列",
textStyle = LegadoTheme.typography.titleSmall, textStyle = LegadoTheme.typography.titleSmall,
backgroundColor = LegadoTheme.colorScheme.onSheetContent,
verticalPadding = 4.dp, verticalPadding = 4.dp,
horizontalPadding = 12.dp, horizontalPadding = 12.dp,
cornerRadius = 12.dp cornerRadius = 12.dp
) )
} }
Slider( AppSlider(
value = gridColumnCount.toFloat(), value = state.gridCount.toFloat(),
onValueChange = { onValueChange = {
val col = it.toInt().coerceIn(1, 10) viewModel.onIntent(ExploreShowIntent.SaveGridCount(it.toInt().coerceIn(1, 10)))
viewModel.saveGridCount(col)
}, },
valueRange = 1f..10f, valueRange = 1f..10f,
steps = 8, steps = 8,
modifier = Modifier.padding(horizontal = 20.dp) modifier = Modifier.padding(horizontal = 20.dp)
) )
Spacer(Modifier.height(24.dp)) Spacer(modifier = Modifier.height(32.dp))
OutlinedButton(
onClick = { showGridCountSheet = false },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
AppText("完成")
} }
}
ExploreKindSelectSheet( ExploreKindSelectSheet(
show = showKindSheet, show = state.sheet == ExploreShowSheet.KindSelect,
onDismissRequest = { showKindSheet = false }, onDismissRequest = { viewModel.onIntent(ExploreShowIntent.DismissSheet) },
sourceUrl = sourceUrl, sourceUrl = state.sourceUrl,
onSelected = { selectedKinds -> onSelected = { selectedKinds ->
selectedKinds.firstOrNull()?.let { kind -> selectedKinds.firstOrNull()?.let { kind ->
viewModel.switchExploreUrl(kind) viewModel.onIntent(ExploreShowIntent.SwitchKind(kind))
} }
} }
) )
@@ -245,23 +227,12 @@ fun ExploreShowScreen(
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
modifier = Modifier.responsiveHazeEffect( modifier = Modifier.responsiveHazeEffect(state = hazeState),
state = hazeState title = state.selectedKindTitle ?: title,
),
title = selectedTitle ?: title,
navigationIcon = { navigationIcon = {
TopBarNavigationButton(onClick = onBack) TopBarNavigationButton(onClick = onBack)
}, },
actions = { actions = {
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.animateContentSize(tween(300))
) {
TopBarActionButton(
onClick = { showKindSheet = true },
imageVector = Icons.Outlined.FilterAlt,
contentDescription = "分类"
)
AnimatedVisibility( AnimatedVisibility(
visible = isGridMode, visible = isGridMode,
@@ -269,15 +240,26 @@ fun ExploreShowScreen(
exit = fadeOut(tween(300)) exit = fadeOut(tween(300))
) { ) {
TopBarActionButton( TopBarActionButton(
onClick = { showGridCountSheet = true }, onClick = {
viewModel.onIntent(
ExploreShowIntent.ShowSheet(
ExploreShowSheet.GridCount
)
)
},
imageVector = Icons.AutoMirrored.Outlined.FormatListBulleted, imageVector = Icons.AutoMirrored.Outlined.FormatListBulleted,
contentDescription = "列数设置" contentDescription = "列数设置"
) )
} }
}
TopBarActionButton( TopBarActionButton(
onClick = { viewModel.setLayout() }, onClick = { viewModel.onIntent(ExploreShowIntent.ShowSheet(ExploreShowSheet.KindSelect)) },
imageVector = Icons.Outlined.FilterAlt,
contentDescription = "分类"
)
TopBarActionButton(
onClick = { viewModel.onIntent(ExploreShowIntent.ToggleLayout) },
imageVector = if (!isGridMode) Icons.AutoMirrored.Outlined.FormatListBulleted else Icons.Default.GridView, imageVector = if (!isGridMode) Icons.AutoMirrored.Outlined.FormatListBulleted else Icons.Default.GridView,
contentDescription = "切换布局" contentDescription = "切换布局"
) )
@@ -288,8 +270,8 @@ fun ExploreShowScreen(
) { paddingValues -> ) { paddingValues ->
AppPullToRefresh( AppPullToRefresh(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
isRefreshing = isRefreshing, isRefreshing = state.isRefreshing,
onRefresh = { viewModel.loadMore(isRefresh = true) }, onRefresh = { viewModel.onIntent(ExploreShowIntent.Refresh) },
topPadding = paddingValues.calculateTopPadding() topPadding = paddingValues.calculateTopPadding()
) { ) {
Crossfade( Crossfade(
@@ -303,7 +285,7 @@ fun ExploreShowScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.responsiveHazeSource(hazeState), .responsiveHazeSource(hazeState),
columns = GridCells.Fixed(gridColumnCount), columns = GridCells.Fixed(state.gridCount),
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding() + 12.dp, top = paddingValues.calculateTopPadding() + 12.dp,
bottom = paddingValues.calculateBottomPadding() + 12.dp, bottom = paddingValues.calculateBottomPadding() + 12.dp,
@@ -324,7 +306,14 @@ fun ExploreShowScreen(
ExploreBookGridItem( ExploreBookGridItem(
book = item.book, book = item.book,
shelfState = item.shelfState, shelfState = item.shelfState,
onClick = { onBookClick(item.book, sharedCoverKey) }, onClick = {
viewModel.onIntent(
ExploreShowIntent.OpenBook(
item.book,
sharedCoverKey
)
)
},
onLongClick = { book, coverKey -> onLongClick = { book, coverKey ->
previewBook = book previewBook = book
previewSharedCoverKey = coverKey previewSharedCoverKey = coverKey
@@ -338,10 +327,10 @@ fun ExploreShowScreen(
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
LoadMoreFooter( LoadMoreFooter(
isLoading = isLoading, isLoading = state.isLoading,
errorMsg = errorMsg, errorMsg = state.errorMsg,
isEnd = isBookEnd, isEnd = state.isEnd,
onRetry = viewModel::loadMore onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) }
) )
} }
} }
@@ -367,7 +356,14 @@ fun ExploreShowScreen(
ExploreBookItem( ExploreBookItem(
book = item.book, book = item.book,
shelfState = item.shelfState, shelfState = item.shelfState,
onClick = { onBookClick(item.book, sharedCoverKey) }, onClick = {
viewModel.onIntent(
ExploreShowIntent.OpenBook(
item.book,
sharedCoverKey
)
)
},
onLongClick = { book, coverKey -> onLongClick = { book, coverKey ->
previewBook = book previewBook = book
previewSharedCoverKey = coverKey previewSharedCoverKey = coverKey
@@ -381,15 +377,14 @@ fun ExploreShowScreen(
item { item {
LoadMoreFooter( LoadMoreFooter(
isLoading = isLoading, isLoading = state.isLoading,
errorMsg = errorMsg, errorMsg = state.errorMsg,
isEnd = isBookEnd, isEnd = state.isEnd,
onRetry = viewModel::loadMore onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) }
) )
} }
} }
} }
} }
} }
} }
@@ -408,7 +403,7 @@ fun ExploreShowScreen(
onBookClick(book, sharedCoverKey) onBookClick(book, sharedCoverKey)
}, },
onAddToShelf = { book -> onAddToShelf = { book ->
viewModel.onAddToShelf(book) viewModel.onIntent(ExploreShowIntent.AddToShelf(book))
}, },
) )
} }
@@ -454,10 +449,9 @@ fun ExploreBookGridItem(
shelfState = shelfState, shelfState = shelfState,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
modifier = modifier, modifier = modifier.padding(4.dp),
sharedTransitionScope = sharedTransitionScope, sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope, animatedVisibilityScope = animatedVisibilityScope,
sharedCoverKey = sharedCoverKey sharedCoverKey = sharedCoverKey
) )
} }
@@ -6,7 +6,6 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.data.repository.ExploreRepository import io.legado.app.data.repository.ExploreRepository
import io.legado.app.domain.model.BookShelfState
import io.legado.app.domain.usecase.AddToBookshelfUseCase import io.legado.app.domain.usecase.AddToBookshelfUseCase
import io.legado.app.domain.usecase.BookShelfKey import io.legado.app.domain.usecase.BookShelfKey
import io.legado.app.domain.usecase.ExploreBooksUseCase import io.legado.app.domain.usecase.ExploreBooksUseCase
@@ -15,43 +14,16 @@ import io.legado.app.domain.usecase.SaveSearchBooksUseCase
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.utils.exploreLayoutGrid import io.legado.app.utils.exploreLayoutGrid
import io.legado.app.utils.stackTraceStr import io.legado.app.utils.stackTraceStr
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import splitties.init.appCtx import splitties.init.appCtx
sealed class BookFilterState(val id: Int) {
data object SHOW_ALL : BookFilterState(0)
data object HIDE_IN_SHELF : BookFilterState(1)
data object HIDE_SAME_NAME_AUTHOR : BookFilterState(2)
data object SHOW_NOT_IN_SHELF_ONLY : BookFilterState(3)
companion object {
fun fromId(id: Int): BookFilterState = when (id) {
1 -> HIDE_IN_SHELF
2 -> HIDE_SAME_NAME_AUTHOR
3 -> SHOW_NOT_IN_SHELF_ONLY
else -> SHOW_ALL
}
}
}
sealed class UiState<out T> {
data object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
data object Empty : UiState<Nothing>()
}
data class ExploreBookItemUi(
val book: SearchBook,
val shelfState: BookShelfState = BookShelfState.NOT_IN_SHELF,
)
class ExploreShowViewModel( class ExploreShowViewModel(
private val repository: ExploreRepository, private val repository: ExploreRepository,
private val resolveBookShelfStateUseCase: ResolveBookShelfStateUseCase, private val resolveBookShelfStateUseCase: ResolveBookShelfStateUseCase,
@@ -61,72 +33,128 @@ class ExploreShowViewModel(
) : ViewModel() { ) : ViewModel() {
private val _rawBooks = MutableStateFlow<List<SearchBook>>(emptyList()) private val _rawBooks = MutableStateFlow<List<SearchBook>>(emptyList())
private val _bookshelf = MutableStateFlow<Set<BookShelfKey>>(emptySet())
private val _isLoading = MutableStateFlow(false) private val _isLoading = MutableStateFlow(false)
private val _isRefreshing = MutableStateFlow(false) private val _isEnd = MutableStateFlow(false)
private val _errorMsg = MutableStateFlow<String?>(null) private val _errorMsg = MutableStateFlow<String?>(null)
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
private val _selectedKindTitle = MutableStateFlow<String?>(null)
private var bookSource: BookSource? = null private var bookSource: BookSource? = null
private var sourceUrl: String? = null private var sourceUrl: String? = null
private var exploreUrl: String? = null private var exploreUrl: String? = null
private var page = 1 private var page = 1
private val _isEndStateFlow = MutableStateFlow(false)
private val _bookshelf = MutableStateFlow<Set<BookShelfKey>>(emptySet())
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
val kinds = _kinds.asStateFlow()
private val _selectedKindTitle = MutableStateFlow<String?>(null)
val selectedKindTitle = _selectedKindTitle.asStateFlow()
private val _layoutState = MutableStateFlow(AppConfig.exploreLayoutState) // 0=列表, 1=网格
val layoutState: StateFlow<Int> = _layoutState.asStateFlow()
private val _gridCount = MutableStateFlow(appCtx.exploreLayoutGrid)
val gridCount = _gridCount.asStateFlow()
val isEnd: StateFlow<Boolean> = _isEndStateFlow.asStateFlow()
val isRefreshing = _isRefreshing.asStateFlow() private val _uiState = MutableStateFlow(
ExploreShowUiState(
layoutState = AppConfig.exploreLayoutState,
gridCount = appCtx.exploreLayoutGrid,
)
)
val uiState = _uiState.asStateFlow()
fun saveGridCount(value: Int) { private val _effects = MutableSharedFlow<ExploreShowEffect>(extraBufferCapacity = 16)
appCtx.exploreLayoutGrid = value val effects = _effects.asSharedFlow()
_gridCount.value = value
init {
observeBookshelf()
combineUiState()
} }
val uiBooks = combine( fun onIntent(intent: ExploreShowIntent) {
when (intent) {
is ExploreShowIntent.InitData -> initData(intent.sourceUrl, intent.exploreUrl)
ExploreShowIntent.LoadMore -> loadMore()
ExploreShowIntent.Refresh -> loadMore(isRefresh = true)
is ExploreShowIntent.SwitchKind -> switchKind(intent.kind)
ExploreShowIntent.ToggleLayout -> toggleLayout()
is ExploreShowIntent.SaveGridCount -> saveGridCount(intent.count)
is ExploreShowIntent.ShowSheet -> _uiState.update { it.copy(sheet = intent.sheet) }
ExploreShowIntent.DismissSheet -> _uiState.update { it.copy(sheet = ExploreShowSheet.None) }
is ExploreShowIntent.OpenBook -> emitEffect(
ExploreShowEffect.OpenBookInfo(
name = intent.book.name,
author = intent.book.author,
bookUrl = intent.book.bookUrl,
origin = intent.book.origin,
coverPath = intent.book.coverUrl,
sharedCoverKey = intent.sharedCoverKey,
)
)
is ExploreShowIntent.AddToShelf -> viewModelScope.launch {
addToBookshelfUseCase.execute(intent.book)
}
}
}
private fun observeBookshelf() {
viewModelScope.launch {
repository.getBookshelfItems().collect { list ->
_bookshelf.value = list.map {
BookShelfKey(it.name, it.author, it.bookUrl)
}.toSet()
}
}
}
private fun combineUiState() {
viewModelScope.launch {
combine(
_rawBooks, _rawBooks,
_bookshelf _bookshelf,
) { books, bookshelf -> _isLoading,
books.map { item -> _isEnd,
_errorMsg,
_kinds,
_selectedKindTitle,
) { values ->
@Suppress("UNCHECKED_CAST")
val rawBooks = values[0] as List<SearchBook>
val bookshelf = values[1] as Set<BookShelfKey>
val isLoading = values[2] as Boolean
val isEnd = values[3] as Boolean
val errorMsg = values[4] as String?
val kinds = values[5] as List<ExploreKind>
val selectedKindTitle = values[6] as String?
val books = rawBooks.map { item ->
ExploreBookItemUi( ExploreBookItemUi(
book = item, book = item,
shelfState = resolveBookShelfStateUseCase.execute( shelfState = resolveBookShelfStateUseCase.execute(
name = item.name, name = item.name,
author = item.author, author = item.author,
url = item.bookUrl, url = item.bookUrl,
shelf = bookshelf shelf = bookshelf,
) )
) )
} }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val shouldTriggerAutoLoad = combine( ExploreShowUiState(
_isLoading, sourceUrl = sourceUrl,
uiBooks, books = books.toImmutableList(),
_rawBooks, kinds = kinds.toImmutableList(),
_isEndStateFlow selectedKindTitle = selectedKindTitle,
) { loading, uiBooks, rawBooks, isEnd -> layoutState = _uiState.value.layoutState,
!loading && uiBooks.isEmpty() && rawBooks.isNotEmpty() && !isEnd gridCount = _uiState.value.gridCount,
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) isLoading = isLoading,
isRefreshing = isLoading && page == 1,
isEnd = isEnd,
errorMsg = errorMsg,
sheet = _uiState.value.sheet,
)
}.collect { newState ->
val oldState = _uiState.value
_uiState.value = newState
val isLoading = _isLoading.asStateFlow() if (newState.books.isEmpty() && _rawBooks.value.isNotEmpty() && !newState.isEnd && !newState.isLoading) {
val errorMsg = _errorMsg.asStateFlow() loadMore()
}
init {
viewModelScope.launch {
repository.getBookshelfItems().collect { list ->
val keys = list.map { BookShelfKey(it.name, it.author, it.bookUrl) }.toSet()
_bookshelf.value = keys
} }
} }
} }
fun initData(incomingSourceUrl: String?, incomingExploreUrl: String?) { private fun initData(incomingSourceUrl: String?, incomingExploreUrl: String?) {
// 允许 incomingExploreUrl 为空,此时加载书源默认发现页
if (sourceUrl == incomingSourceUrl && exploreUrl == incomingExploreUrl && bookSource != null) { if (sourceUrl == incomingSourceUrl && exploreUrl == incomingExploreUrl && bookSource != null) {
return return
} }
@@ -135,16 +163,16 @@ class ExploreShowViewModel(
page = 1 page = 1
bookSource = null bookSource = null
_rawBooks.value = emptyList() _rawBooks.value = emptyList()
_isEndStateFlow.value = false _isEnd.value = false
_errorMsg.value = null _errorMsg.value = null
_selectedKindTitle.value = null _selectedKindTitle.value = null
_kinds.value = emptyList()
viewModelScope.launch { viewModelScope.launch {
if (bookSource == null && incomingSourceUrl != null) { if (bookSource == null && incomingSourceUrl != null) {
bookSource = repository.getBookSource(incomingSourceUrl) bookSource = repository.getBookSource(incomingSourceUrl)
} }
// 如果仍然没有发现 URL,且书源已加载,尝试使用书源的默认发现页
if (exploreUrl == null && bookSource != null) { if (exploreUrl == null && bookSource != null) {
loadKinds(incomingSourceUrl!!) loadKinds(incomingSourceUrl!!)
} }
@@ -153,33 +181,36 @@ class ExploreShowViewModel(
} }
} }
fun loadKinds(sourceUrl: String) { private fun loadKinds(sourceUrl: String) {
viewModelScope.launch { viewModelScope.launch {
_kinds.value = repository.getSourceExploreKinds(sourceUrl) _kinds.value = repository.getSourceExploreKinds(sourceUrl)
} }
} }
fun refreshKinds() { private fun switchKind(kind: ExploreKind) {
sourceUrl?.let { loadKinds(it) }
}
fun switchExploreUrl(kind: ExploreKind) {
_selectedKindTitle.value = kind.title _selectedKindTitle.value = kind.title
exploreUrl = kind.url exploreUrl = kind.url
_isEndStateFlow.value = false _isEnd.value = false
loadMore(isRefresh = true) loadMore(isRefresh = true)
} }
fun setLayout() { private fun toggleLayout() {
val newState = if (_layoutState.value == 0) 1 else 0 _uiState.update {
_layoutState.value = newState val newState = if (it.layoutState == 0) 1 else 0
AppConfig.exploreLayoutState = newState AppConfig.exploreLayoutState = newState
it.copy(layoutState = newState)
}
} }
fun loadMore(isRefresh: Boolean = false) { private fun saveGridCount(count: Int) {
appCtx.exploreLayoutGrid = count
_uiState.update { it.copy(gridCount = count) }
}
private fun loadMore(isRefresh: Boolean = false) {
val source = bookSource val source = bookSource
val url = exploreUrl ?: source?.exploreUrl val url = exploreUrl ?: source?.exploreUrl
if (source == null || url == null || _isLoading.value || (_isEndStateFlow.value && !isRefresh)) return if (source == null || url == null || _isLoading.value || (_isEnd.value && !isRefresh)) return
viewModelScope.launch { viewModelScope.launch {
_isLoading.value = true _isLoading.value = true
@@ -187,7 +218,7 @@ class ExploreShowViewModel(
if (isRefresh) { if (isRefresh) {
page = 1 page = 1
_isEndStateFlow.value = false _isEnd.value = false
_rawBooks.value = emptyList() _rawBooks.value = emptyList()
} }
@@ -195,7 +226,7 @@ class ExploreShowViewModel(
exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page) exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page)
}.onSuccess { result -> }.onSuccess { result ->
if (result.books.isEmpty()) { if (result.books.isEmpty()) {
_isEndStateFlow.value = true _isEnd.value = true
} else { } else {
saveSearchBooksUseCase.save(result.books) saveSearchBooksUseCase.save(result.books)
@@ -207,15 +238,14 @@ class ExploreShowViewModel(
.distinctBy { it.bookUrl } .distinctBy { it.bookUrl }
if (uniqueNewBooks.isEmpty()) { if (uniqueNewBooks.isEmpty()) {
_isEndStateFlow.value = true _isEnd.value = true
} else { } else {
_rawBooks.value = currentList + uniqueNewBooks _rawBooks.value = currentList + uniqueNewBooks
page++ page++
_isEndStateFlow.value = false _isEnd.value = false
} }
} }
} }.onFailure {
.onFailure {
_errorMsg.value = it.stackTraceStr _errorMsg.value = it.stackTraceStr
} }
@@ -223,18 +253,7 @@ class ExploreShowViewModel(
} }
} }
fun getCurrentBookShelfState(item: SearchBook): BookShelfState { private fun emitEffect(effect: ExploreShowEffect) {
return resolveBookShelfStateUseCase.execute( _effects.tryEmit(effect)
name = item.name,
author = item.author,
url = item.bookUrl,
shelf = _bookshelf.value
)
}
fun onAddToShelf(book: SearchBook) {
viewModelScope.launch {
addToBookshelfUseCase.execute(book)
}
} }
} }
@@ -34,7 +34,7 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog 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.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.image.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.modalBottomSheet.AppModalBottomSheet
@@ -236,11 +236,11 @@ fun GroupDeleteAction(
) { ) {
var showDeleteDialog by remember { mutableStateOf(false) } var showDeleteDialog by remember { mutableStateOf(false) }
MediumIconButton( MediumPlainButton(
onClick = { onClick = {
showDeleteDialog = true showDeleteDialog = true
}, },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
AppAlertDialog( AppAlertDialog(
@@ -266,7 +266,7 @@ fun GroupResetCoverAction(
onCoverPathChange: (String?) -> Unit, onCoverPathChange: (String?) -> Unit,
viewModel: GroupViewModel = koinViewModel() viewModel: GroupViewModel = koinViewModel()
) { ) {
MediumIconButton( MediumPlainButton(
onClick = { onClick = {
if (group != null) { if (group != null) {
viewModel.clearCover(group) { viewModel.clearCover(group) {
@@ -277,6 +277,6 @@ fun GroupResetCoverAction(
onCoverPathChange(null) onCoverPathChange(null)
} }
}, },
imageVector = Icons.Default.Restore icon = Icons.Default.Restore
) )
} }
@@ -61,8 +61,8 @@ import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SelectionActions import io.legado.app.ui.widget.components.SelectionActions
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.GlassCard 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.card.TextCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
@@ -427,9 +427,9 @@ private fun ImportPathNavigationBar(
} }
if (canGoBack) { if (canGoBack) {
SmallTonalIconButton( SmallTonalButton(
onClick = onNavigateBack, onClick = onNavigateBack,
imageVector = Icons.AutoMirrored.Filled.ArrowBack, icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "back" contentDescription = "back"
) )
} }
@@ -511,9 +511,9 @@ private fun ImportBookItem(
if (!item.isDir && !item.isOnBookShelf) { if (!item.isDir && !item.isOnBookShelf) {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
SmallIconButton( SmallPlainButton(
onClick = onAddToBookshelf, onClick = onAddToBookshelf,
imageVector = if (isSelected) Icons.Default.Check else Icons.Default.AddCircleOutline icon = if (isSelected) Icons.Default.Check else Icons.Default.AddCircleOutline
) )
} }
} }
@@ -76,9 +76,9 @@ import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SelectionActions import io.legado.app.ui.widget.components.SelectionActions
import io.legado.app.ui.widget.components.alert.AppAlertDialog 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.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.GlassCard 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.card.SelectionItemCard
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
@@ -167,9 +167,9 @@ fun RemoteBookScreen(
}, },
endAction = if (showSheet is RemoteBookSheet.Servers) { endAction = if (showSheet is RemoteBookSheet.Servers) {
{ {
MediumIconButton( MediumPlainButton(
onClick = { showSheet = RemoteBookSheet.ServerConfig(null) }, onClick = { showSheet = RemoteBookSheet.ServerConfig(null) },
imageVector = Icons.Default.Add icon = Icons.Default.Add
) )
} }
} else { } else {
@@ -430,16 +430,16 @@ private fun ServerItem(
{ {
Row { Row {
onEdit?.let { onEdit?.let {
SmallIconButton( SmallPlainButton(
onClick = it, onClick = it,
imageVector = Icons.Default.Edit, icon = Icons.Default.Edit,
contentDescription = "Edit" contentDescription = "Edit"
) )
} }
onDelete?.let { onDelete?.let {
SmallIconButton( SmallPlainButton(
onClick = it, onClick = it,
imageVector = Icons.Default.Delete, icon = Icons.Default.Delete,
contentDescription = "Delete" contentDescription = "Delete"
) )
} }
@@ -602,9 +602,9 @@ private fun PathNavigationBar(
} }
if (canGoBack) { if (canGoBack) {
SmallTonalIconButton( SmallTonalButton(
onClick = onNavigateBack, onClick = onNavigateBack,
imageVector = Icons.AutoMirrored.Filled.ArrowBack, icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "返回上级" contentDescription = "返回上级"
) )
} }
@@ -703,7 +703,7 @@ private fun RemoteBookItem(
if (!book.isDir) { if (!book.isDir) {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
if (book.isOnBookShelf) { if (book.isOnBookShelf) {
onUpdateClick(book) onUpdateClick(book)
@@ -711,7 +711,7 @@ private fun RemoteBookItem(
onAddClick(book) onAddClick(book)
} }
}, },
imageVector = if (book.isOnBookShelf) icon = if (book.isOnBookShelf)
Icons.Outlined.CloudSync Icons.Outlined.CloudSync
else else
Icons.Outlined.AddCircleOutline, Icons.Outlined.AddCircleOutline,
@@ -89,7 +89,7 @@ import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.GlassCard 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.card.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
@@ -1122,9 +1122,9 @@ private fun RelatedBooksBanner(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
SmallTonalIconButton( SmallTonalButton(
onClick = onMoreClick, onClick = onMoreClick,
imageVector = Icons.AutoMirrored.Filled.ArrowForward, icon = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = "more", contentDescription = "more",
) )
} }
@@ -72,8 +72,8 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.GlassCard 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.card.SelectionItemCard
import io.legado.app.ui.widget.components.checkBox.AppCheckbox import io.legado.app.ui.widget.components.checkBox.AppCheckbox
@@ -144,15 +144,15 @@ fun GroupSelectSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.group_select), title = stringResource(R.string.group_select),
startAction = { startAction = {
MediumIconButton( MediumPlainButton(
onClick = { editingGroup = BookGroup() }, onClick = { editingGroup = BookGroup() },
imageVector = Icons.Default.Add icon = Icons.Default.Add
) )
}, },
endAction = { endAction = {
MediumIconButton( MediumPlainButton(
onClick = { onConfirm(selectedGroupId) }, onClick = { onConfirm(selectedGroupId) },
imageVector = Icons.Default.Check icon = Icons.Default.Check
) )
} }
) { ) {
@@ -186,9 +186,9 @@ fun GroupSelectSheet(
) )
}, },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { editingGroup = group }, onClick = { editingGroup = group },
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
}, },
containerColor = LegadoTheme.colorScheme.surfaceContainerLow containerColor = LegadoTheme.colorScheme.surfaceContainerLow
@@ -228,9 +228,9 @@ fun ChangeCoverSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.change_cover_source), title = stringResource(R.string.change_cover_source),
endAction = { endAction = {
MediumIconButton( MediumPlainButton(
onClick = { viewModel.startOrStopSearch() }, onClick = { viewModel.startOrStopSearch() },
imageVector = if (isSearching) Icons.Default.MoreVert else Icons.Default.Refresh icon = if (isSearching) Icons.Default.MoreVert else Icons.Default.Refresh
) )
} }
) { ) {
@@ -362,9 +362,9 @@ fun ChangeSourceSheet(
startAction = { startAction = {
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
Box { Box {
MediumIconButton( MediumPlainButton(
onClick = { showOptionsMenu = true }, onClick = { showOptionsMenu = true },
imageVector = Icons.Default.MoreVert icon = Icons.Default.MoreVert
) )
RoundDropdownMenu( RoundDropdownMenu(
expanded = showOptionsMenu, expanded = showOptionsMenu,
@@ -411,21 +411,21 @@ fun ChangeSourceSheet(
) )
} }
} }
MediumIconButton( MediumPlainButton(
onClick = { showMigrationOptions = true }, onClick = { showMigrationOptions = true },
imageVector = Icons.Outlined.Settings icon = Icons.Outlined.Settings
) )
} }
}, },
endAction = { endAction = {
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
MediumIconButton( MediumPlainButton(
onClick = { viewModel.startOrStopSearch() }, onClick = { viewModel.startOrStopSearch() },
imageVector = if (isSearching) Icons.Default.PauseCircleOutline else Icons.Default.Refresh, icon = if (isSearching) Icons.Default.PauseCircleOutline else Icons.Default.Refresh,
) )
MediumIconButton( MediumPlainButton(
onClick = { showFilterSheet = true }, onClick = { showFilterSheet = true },
imageVector = Icons.Default.FilterList icon = Icons.Default.FilterList
) )
} }
} }
@@ -476,11 +476,11 @@ fun ChangeSourceSheet(
containerColor = LegadoTheme.colorScheme.onSheetContent, containerColor = LegadoTheme.colorScheme.onSheetContent,
selectedContainerColor = LegadoTheme.colorScheme.primaryContainer.copy(alpha = 0.32f), selectedContainerColor = LegadoTheme.colorScheme.primaryContainer.copy(alpha = 0.32f),
leadingContent = { leadingContent = {
MediumIconButton( MediumPlainButton(
onClick = { onClick = {
viewModel.onBookScoreClick(item) viewModel.onBookScoreClick(item)
}, },
imageVector = Icons.Default.PushPin, icon = Icons.Default.PushPin,
tint = if (bookScore > 0) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.outline, tint = if (bookScore > 0) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.outline,
contentDescription = null contentDescription = null
) )
@@ -59,7 +59,7 @@ import io.legado.app.ui.theme.fadingEdge
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog 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.button.series.MediumOutlinedButton
import io.legado.app.ui.widget.components.image.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.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -156,7 +156,7 @@ fun BookInfoEditContent(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { onClick = {
(context as? BookInfoEditActivity)?.showDialogFragment( (context as? BookInfoEditActivity)?.showDialogFragment(
ChangeCoverDialog( ChangeCoverDialog(
@@ -165,15 +165,15 @@ fun BookInfoEditContent(
) )
) )
}, },
imageVector = Icons.Default.ImageSearch icon = Icons.Default.ImageSearch
) )
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { selectCover.launch() }, onClick = { selectCover.launch() },
imageVector = Icons.Default.FolderOpen icon = Icons.Default.FolderOpen
) )
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { viewModel.resetCover() }, onClick = { viewModel.resetCover() },
imageVector = Icons.Default.Replay icon = Icons.Default.Replay
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -371,19 +371,19 @@ fun KindEditor(
} }
} }
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { onClick = {
onReset() onReset()
}, },
imageVector = Icons.Default.Replay icon = Icons.Default.Replay
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { onClick = {
editingIndex = -1 editingIndex = -1
editText = "" editText = ""
}, },
imageVector = Icons.Default.Add icon = Icons.Default.Add
) )
} }
@@ -82,8 +82,7 @@ import io.legado.app.ui.widget.components.AppFloatingActionButtonMenu
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.FabMenuItem import io.legado.app.ui.widget.components.FabMenuItem
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.button.SmallTonalTextButton
import io.legado.app.ui.widget.components.card.GlassCard 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.ReorderableSelectionItem
import io.legado.app.ui.widget.components.card.SelectionItemCard import io.legado.app.ui.widget.components.card.SelectionItemCard
@@ -768,32 +767,32 @@ private fun BookshelfManageScreen(
} }
) )
} }
SmallTonalIconButton( SmallTonalButton(
onClick = { onClick = {
if (!book.isLocal) { if (!book.isLocal) {
viewModel.dispatch(BookshelfManageScreenIntent.ToggleBookDownload(book)) viewModel.dispatch(BookshelfManageScreenIntent.ToggleBookDownload(book))
} }
}, },
imageVector = if (isDownloading) Icons.Default.Stop else Icons.Default.Download, icon = if (isDownloading) Icons.Default.Stop else Icons.Default.Download,
contentDescription = "download" contentDescription = "download"
) )
SmallTonalIconButton( SmallTonalButton(
onClick = { exportBook(book) }, onClick = { exportBook(book) },
imageVector = Icons.Default.Upload, icon = Icons.Default.Upload,
contentDescription = "upload" contentDescription = "upload"
) )
SmallTonalIconButton( SmallTonalButton(
onClick = { onClick = {
pendingMoveGroupBookUrl = book.bookUrl pendingMoveGroupBookUrl = book.bookUrl
groupPickerCurrentGroupId = book.group.coerceAtLeast(0L) groupPickerCurrentGroupId = book.group.coerceAtLeast(0L)
showGroupSelectSheet = true showGroupSelectSheet = true
}, },
imageVector = Icons.Default.Bookmarks, icon = Icons.Default.Bookmarks,
contentDescription = "group" contentDescription = "group"
) )
SmallTonalIconButton( SmallTonalButton(
onClick = { moreMenuBookUrl = book.bookUrl }, onClick = { moreMenuBookUrl = book.bookUrl },
imageVector = Icons.Default.MoreVert, icon = Icons.Default.MoreVert,
contentDescription = "more" contentDescription = "more"
) )
} }
@@ -1288,12 +1287,12 @@ private fun BookSourcePickerSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = title, title = title,
endAction = { endAction = {
SmallTonalTextButton( SmallTonalButton(
text = stringResource(android.R.string.ok),
imageVector = Icons.Default.PlayArrow,
onClick = { onClick = {
onConfirm(selectedSources.mapNotNull { it.getBookSource() }) onConfirm(selectedSources.mapNotNull { it.getBookSource() })
} },
icon = Icons.Default.PlayArrow,
text = stringResource(android.R.string.ok)
) )
} }
) { ) {
@@ -1402,17 +1401,17 @@ private fun BatchChangePreviewSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = "批量换源预览", title = "批量换源预览",
startAction = { startAction = {
SmallTonalTextButton( SmallTonalButton(
text = "新增全部",
imageVector = Icons.Default.Add,
onClick = onAddAllToShelf, onClick = onAddAllToShelf,
icon = Icons.Default.Add,
text = "新增全部"
) )
}, },
endAction = { endAction = {
SmallTonalTextButton( SmallTonalButton(
text = "迁移全部",
imageVector = Icons.Default.PlayArrow,
onClick = onMigrateAll, onClick = onMigrateAll,
icon = Icons.Default.PlayArrow,
text = "迁移全部"
) )
} }
) { items -> ) { items ->
@@ -1498,30 +1497,30 @@ private fun BatchChangePreviewRow(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
SmallTonalTextButton( SmallTonalButton(
imageVector = Icons.Default.Search,
onClick = { onManualSearch(item.oldBook) }, onClick = { onManualSearch(item.oldBook) },
icon = Icons.Default.Search
) )
SmallTonalTextButton( SmallTonalButton(
text = "不迁移",
imageVector = Icons.Default.Clear,
onClick = { onSkip(item.oldBook.bookUrl) }, onClick = { onSkip(item.oldBook.bookUrl) },
icon = Icons.Default.Clear,
text = "不迁移"
) )
SmallTonalTextButton( SmallTonalButton(
text = "迁移",
imageVector = Icons.Default.PlayArrow,
onClick = { onMigrate(item.oldBook.bookUrl) }, onClick = { onMigrate(item.oldBook.bookUrl) },
icon = Icons.Default.PlayArrow,
text = "迁移"
) )
SmallTonalTextButton( SmallTonalButton(
text = "新增",
imageVector = Icons.Default.Add,
onClick = { onAddToShelf(item.oldBook.bookUrl) }, onClick = { onAddToShelf(item.oldBook.bookUrl) },
icon = Icons.Default.Add,
text = "新增"
) )
if (item.candidates.size > 1) { if (item.candidates.size > 1) {
SmallTonalTextButton( SmallTonalButton(
text = "查看其他源信息",
imageVector = Icons.Default.Info,
onClick = { onShowOtherSources(item) }, onClick = { onShowOtherSources(item) },
icon = Icons.Default.Info,
text = "查看其他源信息"
) )
} }
} }
@@ -1605,9 +1604,9 @@ private fun OtherSourceOptionsSheet(
isSelected = index == currentItem.selectedCandidateIndex, isSelected = index == currentItem.selectedCandidateIndex,
onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) }, onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) },
trailingAction = { trailingAction = {
SmallTonalIconButton( SmallTonalButton(
onClick = { onOpenBook(candidate.book) }, onClick = { onOpenBook(candidate.book) },
imageVector = Icons.Default.Info, icon = Icons.Default.Info,
contentDescription = null, contentDescription = null,
) )
}, },
@@ -54,7 +54,7 @@ import io.legado.app.ui.book.readRecord.component.StatsGridCard
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.heatmap.HeatmapMode 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.image.cover.CoilBookCover
@@ -187,9 +187,9 @@ fun DateNavigator(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
MediumIconButton( MediumPlainButton(
onClick = onPrevClick, onClick = onPrevClick,
imageVector = Icons.AutoMirrored.Filled.ArrowLeft icon = Icons.AutoMirrored.Filled.ArrowLeft
) )
AnimatedContent( AnimatedContent(
targetState = referenceDate, targetState = referenceDate,
@@ -224,9 +224,9 @@ fun DateNavigator(
modifier = Modifier.padding(horizontal = 16.dp) modifier = Modifier.padding(horizontal = 16.dp)
) )
} }
MediumIconButton( MediumPlainButton(
onClick = onNextClick, onClick = onNextClick,
imageVector = Icons.AutoMirrored.Filled.ArrowRight icon = Icons.AutoMirrored.Filled.ArrowRight
) )
} }
} }
@@ -25,7 +25,7 @@ import io.legado.app.R
import io.legado.app.data.entities.BookSourcePart import io.legado.app.data.entities.BookSourcePart
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.SearchBar import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.MediumPlainButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.card.SelectionItemCard import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -71,7 +71,7 @@ import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.book.SearchBookListItem import io.legado.app.ui.widget.components.book.SearchBookListItem
import io.legado.app.ui.widget.components.book.SearchBookPreviewSheet import io.legado.app.ui.widget.components.book.SearchBookPreviewSheet
import io.legado.app.ui.widget.components.button.SmallPlainButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.NormalCard 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.card.SelectionItemCard
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
@@ -0,0 +1,111 @@
package io.legado.app.ui.book.search
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
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.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.SearchBook
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizonalPadding
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.theme.fadingEdge
import io.legado.app.ui.widget.components.book.SearchBookGridItem
import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SearchSourceSection(
sourceName: String,
items: List<SearchResultItemUi>,
onClickBook: (SearchBook, String?) -> Unit,
onLongClickBook: ((SearchBook, String?) -> Unit)? = null,
onViewAll: (() -> Unit)? = null,
modifier: Modifier = Modifier,
sharedTransitionScope: SharedTransitionScope? = null,
animatedVisibilityScope: AnimatedVisibilityScope? = null,
sourceSectionIndex: Int = 0,
) {
if (items.isEmpty()) return
Column(modifier = modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.adaptiveHorizontalPadding()
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
AppText(
text = sourceName,
style = LegadoTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
TextCard(
text = "${items.size}",
modifier = Modifier
.padding(start = 12.dp)
)
Spacer(modifier = Modifier.weight(1f))
if (onViewAll != null) {
SmallTonalButton(
onClick = onViewAll,
icon = Icons.AutoMirrored.Filled.ArrowForward
)
}
}
val lazyListState = rememberLazyListState()
LazyRow(
state = lazyListState,
modifier = Modifier
.fillMaxWidth()
.fadingEdge(lazyListState, gradientWidth = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = adaptiveHorizonalPadding(),
) {
itemsIndexed(
items = items,
key = { index, item -> "${item.book.origin}:${item.book.bookUrl}:$index" }
) { index, item ->
val sharedCoverKey = bookCoverSharedElementKey(
item.book.bookUrl,
"search_source:$sourceSectionIndex:$index"
)
SearchBookGridItem(
book = item.book,
shelfState = item.shelfState,
onClick = { onClickBook(item.book, sharedCoverKey) },
onLongClick = onLongClickBook,
modifier = Modifier.width(100.dp),
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
sharedCoverKey = sharedCoverKey,
)
}
}
}
}
@@ -59,10 +59,10 @@ import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBar import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.MediumOutlinedButton import io.legado.app.ui.widget.components.button.series.MediumOutlinedButton
import io.legado.app.ui.widget.components.button.SmallPlainButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.button.SmallToggleButton import io.legado.app.ui.widget.components.button.series.SmallToggleButton
import io.legado.app.ui.widget.components.button.ToggleStyle import io.legado.app.ui.widget.components.button.series.ToggleStyle
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -95,7 +95,8 @@ import io.legado.app.ui.widget.components.FabMenuItem
import io.legado.app.ui.widget.components.SelectionBottomBar import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem import io.legado.app.ui.widget.components.bookmark.BookmarkItem
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton import io.legado.app.ui.widget.components.button.series.SmallToggleButton
import io.legado.app.ui.widget.components.button.series.ToggleStyle
import io.legado.app.ui.widget.components.card.NormalCard 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.card.TextCard
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
@@ -476,10 +477,11 @@ fun TocScreen(
if (pagerState.currentPage == 0 && hasVolumes) { if (pagerState.currentPage == 0 && hasVolumes) {
Box { Box {
SmallOutlinedIconToggleButton( SmallToggleButton(
checked = showVolumeMenu, checked = showVolumeMenu,
onCheckedChange = { showVolumeMenu = it }, onCheckedChange = { showVolumeMenu = it },
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted, style = ToggleStyle.Outlined,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.volume_management) contentDescription = stringResource(R.string.volume_management)
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -5,24 +5,15 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -48,7 +39,7 @@ import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.ActionItem import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.DraggableSelectionHandler import io.legado.app.ui.widget.components.DraggableSelectionHandler
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet 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.icon.AppIcons
@@ -59,7 +50,6 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleEditFields import io.legado.app.ui.widget.components.rules.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet import io.legado.app.ui.widget.components.rules.RuleEditSheet
import io.legado.app.ui.widget.components.rules.RuleListScaffold import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -350,9 +340,9 @@ fun TxtRuleScreen(
onEnabledChange = { enabled -> viewModel.update(item.rule.copy(enable = enabled)) }, onEnabledChange = { enabled -> viewModel.update(item.rule.copy(enable = enabled)) },
onClickEdit = { editingRule = item.rule; showEditSheet = true }, onClickEdit = { editingRule = item.rule; showEditSheet = true },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { showDeleteRuleDialog = item.rule }, onClick = { showDeleteRuleDialog = item.rule },
imageVector = AppIcons.Delete icon = AppIcons.Delete
) )
} }
) )
@@ -23,7 +23,7 @@ import io.legado.app.R
import io.legado.app.help.DefaultData import io.legado.app.help.DefaultData
import io.legado.app.model.BookCover import io.legado.app.model.BookCover
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.checkBox.CheckboxItem 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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -59,8 +59,8 @@ fun CoverRuleConfigSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.cover_rule), title = stringResource(R.string.cover_rule),
startAction = { startAction = {
MediumIconButton( MediumPlainButton(
imageVector = Icons.Default.SettingsBackupRestore, icon = Icons.Default.SettingsBackupRestore,
onClick = { onClick = {
DefaultData.coverRule.let { DefaultData.coverRule.let {
enable = it.enable enable = it.enable
@@ -72,8 +72,8 @@ fun CoverRuleConfigSheet(
) )
}, },
endAction = { endAction = {
MediumIconButton( MediumPlainButton(
imageVector = Icons.Default.Save, icon = Icons.Default.Save,
onClick = { onClick = {
if (searchUrl.isBlank() || coverRule.isBlank()) { if (searchUrl.isBlank() || coverRule.isBlank()) {
appCtx.toastOnUi(R.string.cover_rule_fields_required) appCtx.toastOnUi(R.string.cover_rule_fields_required)
@@ -2,7 +2,6 @@ package io.legado.app.ui.config.otherConfig
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -16,12 +15,8 @@ import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -39,7 +34,7 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog 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.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu 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.menuItem.RoundDropdownMenuItem
@@ -70,18 +65,18 @@ fun DirectLinkUploadBottomSheet(
show = show, show = show,
title = stringResource(R.string.direct_link_upload_config), title = stringResource(R.string.direct_link_upload_config),
startAction = { startAction = {
MediumIconButton( MediumPlainButton(
onClick = { onClick = {
viewModel.testRule { result -> showTestResult = result } viewModel.testRule { result -> showTestResult = result }
}, },
imageVector = Icons.Default.Checklist icon = Icons.Default.Checklist
) )
}, },
endAction = { endAction = {
Box { Box {
MediumIconButton( MediumPlainButton(
onClick = { showMenu = true }, onClick = { showMenu = true },
imageVector = Icons.Default.MoreVert icon = Icons.Default.MoreVert
) )
RoundDropdownMenu( RoundDropdownMenu(
expanded = showMenu, expanded = showMenu,
@@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
@@ -114,13 +114,13 @@ fun BackgroundImageManageSheet(
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
} }
SmallTonalIconButton( SmallTonalButton(
onClick = { viewModel.removeBackground(isDark) }, onClick = { viewModel.removeBackground(isDark) },
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(8.dp) .padding(8.dp)
.size(32.dp), .size(32.dp),
imageVector = Icons.Default.Close icon = Icons.Default.Close
) )
} }
} }
@@ -5,21 +5,19 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AutoAwesome import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -30,8 +28,8 @@ import androidx.core.graphics.ColorUtils
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.config.TagColorGenerator import io.legado.app.help.config.TagColorGenerator
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton import io.legado.app.ui.widget.components.button.series.MediumOutlinedButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.NormalCard 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.card.TextCard
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
@@ -57,7 +55,7 @@ fun LabelColorManageSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.theme_config_manage_label_colors), title = stringResource(R.string.theme_config_manage_label_colors),
startAction = { startAction = {
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { onClick = {
val baseColor = if (themeColor != 0) Color(themeColor) else primaryColor val baseColor = if (themeColor != 0) Color(themeColor) else primaryColor
val generatedColors = TagColorGenerator.generateTagColors(baseColor) val generatedColors = TagColorGenerator.generateTagColors(baseColor)
@@ -65,23 +63,25 @@ fun LabelColorManageSheet(
tagColors.addAll(generatedColors) tagColors.addAll(generatedColors)
ThemeConfig.saveCustomTagColors(tagColors) ThemeConfig.saveCustomTagColors(tagColors)
}, },
imageVector = Icons.Default.AutoAwesome icon = Icons.Default.AutoAwesome
) )
}, },
endAction = { endAction = {
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = { onClick = {
tagColors.add(TagColorPair(0, 0)) tagColors.add(TagColorPair(0, 0))
editingIndex = tagColors.size - 1 editingIndex = tagColors.size - 1
editingTextColor = 0 editingTextColor = 0
showColorPicker = true showColorPicker = true
}, },
imageVector = Icons.Default.Add icon = Icons.Default.Add
) )
} }
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp), modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
items(tagColors.size) { index -> items(tagColors.size) { index ->
@@ -110,20 +110,20 @@ fun LabelColorManageSheet(
Row( Row(
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
editingIndex = index editingIndex = index
editingTextColor = colorPair.textColor editingTextColor = colorPair.textColor
showColorPicker = true showColorPicker = true
}, },
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
tagColors.removeAt(index) tagColors.removeAt(index)
ThemeConfig.saveCustomTagColors(tagColors) ThemeConfig.saveCustomTagColors(tagColors)
}, },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
} }
@@ -30,7 +30,7 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -123,13 +123,13 @@ fun NavIconManageSheet(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
SmallTonalIconButton( SmallTonalButton(
onClick = { dest.onSetPath("") }, onClick = { dest.onSetPath("") },
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(4.dp) .padding(4.dp)
.size(24.dp), .size(24.dp),
imageVector = Icons.Default.Close icon = Icons.Default.Close
) )
} }
} else { } else {
@@ -88,7 +88,7 @@ import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
@@ -222,8 +222,8 @@ fun ThemeConfigScreen(
style = LegadoTheme.typography.labelLargeEmphasized, style = LegadoTheme.typography.labelLargeEmphasized,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
SmallIconButton( SmallPlainButton(
imageVector = AppIcons.Close, icon = AppIcons.Close,
contentDescription = "关闭", contentDescription = "关闭",
onClick = { onClick = {
viewModel.setShowThemeRefactorTip(false) viewModel.setShowThemeRefactorTip(false)
@@ -759,8 +759,8 @@ fun ThemeConfigScreen(
onDismissRequest = { showFontSheet = false }, onDismissRequest = { showFontSheet = false },
title = stringResource(R.string.font_setting), title = stringResource(R.string.font_setting),
startAction = { startAction = {
SmallIconButton( SmallPlainButton(
imageVector = Icons.Default.Delete, icon = Icons.Default.Delete,
contentDescription = stringResource(R.string.clear), contentDescription = stringResource(R.string.clear),
onClick = { onClick = {
ThemeConfig.appFontPath = null ThemeConfig.appFontPath = null
@@ -769,8 +769,8 @@ fun ThemeConfigScreen(
) )
}, },
endAction = { endAction = {
SmallIconButton( SmallPlainButton(
imageVector = Icons.Default.Add, icon = Icons.Default.Add,
contentDescription = stringResource(R.string.select_folder), contentDescription = stringResource(R.string.select_folder),
onClick = { fontFolderLauncher.launch(null) } onClick = { fontFolderLauncher.launch(null) }
) )
@@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.config.ThemeExportData import io.legado.app.help.config.ThemeExportData
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.CompactClickableSettingItem import io.legado.app.ui.widget.components.settingItem.CompactClickableSettingItem
@@ -60,13 +60,13 @@ fun EditThemeSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.theme_manage_edit_theme), title = stringResource(R.string.theme_manage_edit_theme),
endAction = { endAction = {
MediumIconButton( MediumPlainButton(
onClick = { onClick = {
if (name.isNotBlank()) { if (name.isNotBlank()) {
onSave(name, data) onSave(name, data)
} }
}, },
imageVector = Icons.Default.Done, icon = Icons.Default.Done,
contentDescription = "Save" contentDescription = "Save"
) )
} }
@@ -47,7 +47,7 @@ import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -393,17 +393,17 @@ private fun SavedThemeItem(
horizontalArrangement = Arrangement.End, horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
SmallIconButton( SmallPlainButton(
onClick = onEdit, onClick = onEdit,
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
SmallIconButton( SmallPlainButton(
onClick = onExport, onClick = onExport,
imageVector = Icons.Default.Share icon = Icons.Default.Share
) )
SmallIconButton( SmallPlainButton(
onClick = onDelete, onClick = onDelete,
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
} }
@@ -38,7 +38,7 @@ import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.ActionItem import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.DraggableSelectionHandler import io.legado.app.ui.widget.components.DraggableSelectionHandler
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet 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.icon.AppIcons
@@ -315,9 +315,9 @@ fun DictRuleScreen(
onEnabledChange = { enabled -> viewModel.update(item.rule.copy(enabled = enabled)) }, onEnabledChange = { enabled -> viewModel.update(item.rule.copy(enabled = enabled)) },
onClickEdit = { editingRule = item.rule; showEditSheet = true }, onClickEdit = { editingRule = item.rule; showEditSheet = true },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { showDeleteRuleDialog = item.rule }, onClick = { showDeleteRuleDialog = item.rule },
imageVector = AppIcons.Delete icon = AppIcons.Delete
) )
} }
) )
@@ -111,7 +111,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
context: Context, context: Context,
exploreName: String? = null, exploreName: String? = null,
sourceUrl: String, sourceUrl: String,
exploreUrl: String? = null exploreUrl: String? = null,
): Intent = MainIntent.createExploreShowIntent(context, exploreName, sourceUrl, exploreUrl) ): Intent = MainIntent.createExploreShowIntent(context, exploreName, sourceUrl, exploreUrl)
} }
@@ -134,7 +134,7 @@ object MainIntent {
context: Context, context: Context,
exploreName: String? = null, exploreName: String? = null,
sourceUrl: String, sourceUrl: String,
exploreUrl: String? = null exploreUrl: String? = null,
): Intent { ): Intent {
return createLauncherIntent(context).apply { return createLauncherIntent(context).apply {
putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_EXPLORE_SHOW) putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_EXPLORE_SHOW)
@@ -19,7 +19,9 @@ import io.legado.app.ui.about.AboutEffect
import io.legado.app.ui.about.AboutScreen import io.legado.app.ui.about.AboutScreen
import io.legado.app.ui.about.AboutViewModel import io.legado.app.ui.about.AboutViewModel
import io.legado.app.ui.book.cache.manage.BookCacheManageRouteScreen import io.legado.app.ui.book.cache.manage.BookCacheManageRouteScreen
import io.legado.app.ui.book.explore.ExploreShowIntent
import io.legado.app.ui.book.explore.ExploreShowScreen import io.legado.app.ui.book.explore.ExploreShowScreen
import io.legado.app.ui.book.explore.ExploreShowViewModel
import io.legado.app.ui.book.import.local.ImportBookScreen import io.legado.app.ui.book.import.local.ImportBookScreen
import io.legado.app.ui.book.import.remote.RemoteBookScreen import io.legado.app.ui.book.import.remote.RemoteBookScreen
import io.legado.app.ui.book.info.BookInfoRouteScreen import io.legado.app.ui.book.info.BookInfoRouteScreen
@@ -39,8 +41,8 @@ import io.legado.app.ui.config.downloadCacheConfig.DownloadCacheConfigScreen
import io.legado.app.ui.config.otherConfig.OtherConfigScreen import io.legado.app.ui.config.otherConfig.OtherConfigScreen
import io.legado.app.ui.config.readConfig.ReadConfigScreen import io.legado.app.ui.config.readConfig.ReadConfigScreen
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
import io.legado.app.ui.config.translation.TranslationConfigScreen
import io.legado.app.ui.config.themeManage.ThemeManageScreen import io.legado.app.ui.config.themeManage.ThemeManageScreen
import io.legado.app.ui.config.translation.TranslationConfigScreen
import io.legado.app.ui.rss.article.MainRouteRssSort import io.legado.app.ui.rss.article.MainRouteRssSort
import io.legado.app.ui.rss.article.RssSortRouteScreen import io.legado.app.ui.rss.article.RssSortRouteScreen
import io.legado.app.ui.rss.favorites.RssFavoritesScreen import io.legado.app.ui.rss.favorites.RssFavoritesScreen
@@ -436,10 +438,17 @@ fun MainActivity.mainEntryProvider(
} }
entry<MainRouteExploreShow> { route -> entry<MainRouteExploreShow> { route ->
val exploreViewModel = koinViewModel<ExploreShowViewModel>()
LaunchedEffect(route.sourceUrl, route.exploreUrl, exploreViewModel) {
exploreViewModel.onIntent(
ExploreShowIntent.InitData(route.sourceUrl, route.exploreUrl)
)
}
ExploreShowScreen( ExploreShowScreen(
viewModel = exploreViewModel,
title = route.title ?: "探索", title = route.title ?: "探索",
sourceUrl = route.sourceUrl,
exploreUrl = route.exploreUrl,
onBack = { onNavigateBack() }, onBack = { onNavigateBack() },
onBookClick = { book, sharedCoverKey -> onBookClick = { book, sharedCoverKey ->
onNavigateToRoute( onNavigateToRoute(
@@ -268,7 +268,7 @@ object MainNavigator {
MainRouteExploreShow( MainRouteExploreShow(
title = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_NAME), title = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_NAME),
sourceUrl = sourceUrl, sourceUrl = sourceUrl,
exploreUrl = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_URL) exploreUrl = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_URL),
) )
} ?: MainRouteHome } ?: MainRouteHome
@@ -114,7 +114,9 @@ import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBar import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.button.series.SmallToggleButton
import io.legado.app.ui.widget.components.button.series.ToggleStyle
import io.legado.app.ui.widget.components.card.NormalCard 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.card.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider import io.legado.app.ui.widget.components.divider.PillHeaderDivider
@@ -557,7 +559,7 @@ fun BookshelfScreen(
val showExpandButton by BookshelfConfig.shouldShowExpandButtonState val showExpandButton by BookshelfConfig.shouldShowExpandButtonState
if (showExpandButton) { if (showExpandButton) {
Box(modifier = Modifier) { Box(modifier = Modifier) {
SmallOutlinedIconToggleButton( SmallToggleButton(
checked = showGroupMenu, checked = showGroupMenu,
onCheckedChange = { onCheckedChange = {
if (it) { if (it) {
@@ -566,7 +568,8 @@ fun BookshelfScreen(
viewModel.dismissOverlay() viewModel.dismissOverlay()
} }
}, },
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted, style = ToggleStyle.Outlined,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.group_manage) contentDescription = stringResource(R.string.group_manage)
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -837,7 +840,7 @@ fun BookshelfScreen(
if (this != null) Modifier.skipToLookaheadSize() else Modifier if (this != null) Modifier.skipToLookaheadSize() else Modifier
} }
), ),
beyondViewportPageCount = 1, beyondViewportPageCount = 0,
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it } key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
) { pageIndex -> ) { pageIndex ->
val group = uiState.groups.getOrNull(pageIndex) val group = uiState.groups.getOrNull(pageIndex)
@@ -1058,9 +1061,10 @@ private fun BookshelfTopBar(
}, },
trailingIcon = { trailingIcon = {
if (uiState.searchKey.isNotEmpty()) { if (uiState.searchKey.isNotEmpty()) {
TopBarActionButton( SmallPlainButton(
modifier = Modifier.padding(start = 12.dp),
onClick = onClearSearch, onClick = onClearSearch,
imageVector = AppIcons.Close, icon = AppIcons.Close,
contentDescription = stringResource(R.string.clear) contentDescription = stringResource(R.string.clear)
) )
} }
@@ -41,7 +41,6 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -196,13 +195,12 @@ class BookshelfViewModel(
}.distinctUntilChanged().flowOn(Dispatchers.Default) }.distinctUntilChanged().flowOn(Dispatchers.Default)
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val allGroupBooksFlow: StateFlow<Map<Long, List<BookUiItem>>> = combine( private val allGroupBooksImmutableFlow: StateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>> =
groupsFlow, sortConfigFlow combine(groupsFlow, sortConfigFlow) { groups, sortConfig ->
) { groups, sortConfig ->
groups to sortConfig groups to sortConfig
}.flatMapLatest { (groups, sortConfig) -> }.flatMapLatest { (groups, sortConfig) ->
if (groups.isEmpty()) { if (groups.isEmpty()) {
flowOf(emptyMap()) flowOf(persistentMapOf())
} else { } else {
val flows = groups.map { group -> val flows = groups.map { group ->
bookRepository.flowBookShelfByGroup(group.groupId).map { books -> bookRepository.flowBookShelfByGroup(group.groupId).map { books ->
@@ -211,19 +209,17 @@ class BookshelfViewModel(
group, group,
sortConfig.sort, sortConfig.sort,
sortConfig.sortOrder sortConfig.sortOrder
).map { it.toUiItem() } ).map { it.toUiItem() }.toImmutableList()
}
}
combine(flows) { results ->
results.fold(persistentMapOf<Long, ImmutableList<BookUiItem>>()) { acc, (id, list) ->
acc.put(id, list)
} }
} }
combine(flows) { it.toMap() }
} }
}.distinctUntilChanged() }.distinctUntilChanged()
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap())
private val allGroupBooksImmutableFlow: StateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>> =
allGroupBooksFlow.map { map ->
map.mapValues { it.value.toImmutableList() }.toImmutableMap()
}.flowOn(Dispatchers.Default)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), persistentMapOf()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), persistentMapOf())
private val visibleBooksFlow: Flow<List<BookUiItem>> = combine( private val visibleBooksFlow: Flow<List<BookUiItem>> = combine(
@@ -301,30 +297,21 @@ class BookshelfViewModel(
} }
}.distinctUntilChanged().flowOn(Dispatchers.Default) }.distinctUntilChanged().flowOn(Dispatchers.Default)
private val coreInternalStateFlow = combine( private val internalStateFlow = combine(
groupIdFlow, groupIdFlow,
searchKeyFlow, searchKeyFlow,
searchModeFlow, searchModeFlow,
loadingTextFlow, loadingTextFlow,
updatingBooksFlow combine(updatingBooksFlow, upBooksCountFlow, sortConfigFlow) { a, b, c ->
) { groupId, searchKey, isSearchMode, loadingText, updatingBooks -> Triple(a, b, c)
}
) { groupId, searchKey, isSearchMode, loadingText, (updatingBooks, upBooksCount, sortConfig) ->
InternalState( InternalState(
groupId = groupId, groupId = groupId,
searchKey = searchKey, searchKey = searchKey,
isSearchMode = isSearchMode, isSearchMode = isSearchMode,
loadingText = loadingText, loadingText = loadingText,
updatingBooks = updatingBooks, updatingBooks = updatingBooks,
upBooksCount = 0,
sortConfig = readSortConfig()
)
}
private val internalStateFlow = combine(
coreInternalStateFlow,
upBooksCountFlow,
sortConfigFlow
) { core, upBooksCount, sortConfig ->
core.copy(
upBooksCount = upBooksCount, upBooksCount = upBooksCount,
sortConfig = sortConfig sortConfig = sortConfig
) )
@@ -351,35 +338,29 @@ class BookshelfViewModel(
val pendingSavedBooks: List<BookUiItem>? val pendingSavedBooks: List<BookUiItem>?
) )
private val editStateFlow = combine( private val interactionStateFlow = combine(
activeOverlayFlow, activeOverlayFlow,
isEditModeFlow, isEditModeFlow,
selectedVisibleBookUrlsFlow, selectedVisibleBookUrlsFlow,
isInFolderRootFlow isInFolderRootFlow,
) { activeOverlay, isEditMode, selectedBookUrls, isInFolderRoot -> isRefreshingFlow
EditState(activeOverlay, isEditMode, selectedBookUrls, isInFolderRoot) ) { activeOverlay, isEditMode, selectedBookUrls, isInFolderRoot, isRefreshing ->
}
private data class EditState(
val activeOverlay: BookshelfOverlay?,
val isEditMode: Boolean,
val selectedBookUrls: Set<String>,
val isInFolderRoot: Boolean
)
private val interactionStateFlow = combine(
editStateFlow,
isRefreshingFlow,
bookGroupStyleFlow,
draggingBooksFlow,
pendingSavedBooksFlow
) { editState, isRefreshing, bookGroupStyle, draggingBooks, pendingSavedBooks ->
BookshelfInteractionState( BookshelfInteractionState(
activeOverlay = editState.activeOverlay, activeOverlay = activeOverlay,
isEditMode = editState.isEditMode, isEditMode = isEditMode,
selectedBookUrls = editState.selectedBookUrls, selectedBookUrls = selectedBookUrls,
isInFolderRoot = editState.isInFolderRoot, isInFolderRoot = isInFolderRoot,
isRefreshing = isRefreshing, isRefreshing = isRefreshing,
bookGroupStyle = 0,
draggingBooks = null,
pendingSavedBooks = null
)
}.combine(
combine(bookGroupStyleFlow, draggingBooksFlow, pendingSavedBooksFlow) { a, b, c ->
Triple(a, b, c)
}
) { interaction, (bookGroupStyle, draggingBooks, pendingSavedBooks) ->
interaction.copy(
bookGroupStyle = bookGroupStyle, bookGroupStyle = bookGroupStyle,
draggingBooks = draggingBooks, draggingBooks = draggingBooks,
pendingSavedBooks = pendingSavedBooks pendingSavedBooks = pendingSavedBooks
@@ -390,11 +371,6 @@ class BookshelfViewModel(
GroupPreviewState(persistentMapOf(), persistentMapOf(), 0) GroupPreviewState(persistentMapOf(), persistentMapOf(), 0)
) )
private val allGroupBooksStateFlow =
MutableStateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>>(
persistentMapOf()
)
private val dataStateFlow = combine( private val dataStateFlow = combine(
booksFlow, booksFlow,
groupsFlow, groupsFlow,
@@ -403,7 +379,7 @@ class BookshelfViewModel(
internalStateFlow internalStateFlow
) { books, groups, allGroups, previews, internal -> ) { books, groups, allGroups, previews, internal ->
BookshelfDataCore(books, groups, allGroups, previews, internal) BookshelfDataCore(books, groups, allGroups, previews, internal)
}.combine(allGroupBooksStateFlow) { core, allGroupBooks -> }.combine(allGroupBooksImmutableFlow) { core, allGroupBooks ->
BookshelfDataState( BookshelfDataState(
books = core.books, books = core.books,
groups = core.groups.map { it.toBookGroupUi() }, groups = core.groups.map { it.toBookGroupUi() },
@@ -529,9 +505,6 @@ class BookshelfViewModel(
viewModelScope.launch { viewModelScope.launch {
groupPreviewsFlow.collect { groupPreviewsStateFlow.value = it } groupPreviewsFlow.collect { groupPreviewsStateFlow.value = it }
} }
viewModelScope.launch {
allGroupBooksImmutableFlow.collect { allGroupBooksStateFlow.value = it }
}
viewModelScope.launch { viewModelScope.launch {
combine(booksFlow, selectedGroupCanReorderFlow) { books, canReorderBooks -> combine(booksFlow, selectedGroupCanReorderFlow) { books, canReorderBooks ->
books to canReorderBooks books to canReorderBooks
@@ -26,12 +26,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.book.group.GroupEditContent
import io.legado.app.ui.book.group.GroupDeleteAction import io.legado.app.ui.book.group.GroupDeleteAction
import io.legado.app.ui.book.group.GroupEditContent
import io.legado.app.ui.book.group.GroupResetCoverAction import io.legado.app.ui.book.group.GroupResetCoverAction
import io.legado.app.ui.book.group.GroupViewModel import io.legado.app.ui.book.group.GroupViewModel
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.move import io.legado.app.utils.move
@@ -95,13 +95,13 @@ fun GroupManageSheet(
}, },
endAction = { endAction = {
if (!isEditing) { if (!isEditing) {
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
editingGroup = null editingGroup = null
coverPath = null coverPath = null
isEditing = true isEditing = true
}, },
imageVector = Icons.Default.Add icon = Icons.Default.Add
) )
} else { } else {
GroupResetCoverAction( GroupResetCoverAction(
@@ -92,6 +92,9 @@ fun ExploreScreen(
val context = LocalContext.current val context = LocalContext.current
val activity = context as? AppCompatActivity val activity = context as? AppCompatActivity
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val listItems by remember(uiState.items, uiState.expandedId, uiState.exploreKinds) {
derivedStateOf { viewModel.buildExploreListItems(uiState) }
}
var sourceToDeleteUrl by rememberSaveable { mutableStateOf<String?>(null) } var sourceToDeleteUrl by rememberSaveable { mutableStateOf<String?>(null) }
val sourceToDelete = remember(sourceToDeleteUrl, uiState.items) { val sourceToDelete = remember(sourceToDeleteUrl, uiState.items) {
uiState.items.firstOrNull { it.bookSourceUrl == sourceToDeleteUrl } uiState.items.firstOrNull { it.bookSourceUrl == sourceToDeleteUrl }
@@ -118,10 +121,10 @@ fun ExploreScreen(
} }
} }
val stickyHeaderSource by remember(uiState.listItems, uiState.items) { val stickyHeaderSource by remember(listItems, uiState.items) {
derivedStateOf { derivedStateOf {
val firstIndex = listState.firstVisibleItemIndex val firstIndex = listState.firstVisibleItemIndex
val item = uiState.listItems.getOrNull(firstIndex) val item = listItems.getOrNull(firstIndex)
if (item is ExploreListItem.KindRow) { if (item is ExploreListItem.KindRow) {
uiState.items.find { it.bookSourceUrl == item.sourceUrl } uiState.items.find { it.bookSourceUrl == item.sourceUrl }
} else { } else {
@@ -178,7 +181,7 @@ fun ExploreScreen(
) )
) { ) {
items( items(
items = uiState.listItems, items = listItems,
key = { it.key } key = { it.key }
) { listItem -> ) { listItem ->
when (listItem) { when (listItem) {
@@ -268,7 +271,7 @@ fun ExploreScreen(
verticalPadding = 8.dp, verticalPadding = 8.dp,
onClick = { onClick = {
scope.launch { scope.launch {
val index = uiState.listItems.indexOfFirst { val index = listItems.indexOfFirst {
it is ExploreListItem.Header && it.source.bookSourceUrl == item.bookSourceUrl it is ExploreListItem.Header && it.source.bookSourceUrl == item.bookSourceUrl
} }
if (index >= 0) listState.animateScrollToItem(index) if (index >= 0) listState.animateScrollToItem(index)
@@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -42,7 +41,6 @@ class ExploreViewModel(
private val _uiState = MutableStateFlow(ExploreUiState()) private val _uiState = MutableStateFlow(ExploreUiState())
val uiState: StateFlow<ExploreUiState> = _uiState val uiState: StateFlow<ExploreUiState> = _uiState
.map { state -> state.copy(listItems = buildExploreListItems(state)) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ExploreUiState()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ExploreUiState())
private val _effects = MutableSharedFlow<ExploreEffect>(extraBufferCapacity = 8) private val _effects = MutableSharedFlow<ExploreEffect>(extraBufferCapacity = 8)
val effects = _effects.asSharedFlow() val effects = _effects.asSharedFlow()
@@ -200,11 +198,10 @@ class ExploreViewModel(
val exploreKinds: ImmutableList<ExploreKind> = persistentListOf(), val exploreKinds: ImmutableList<ExploreKind> = persistentListOf(),
val kindDisplayNames: ImmutableMap<String, String> = persistentMapOf(), val kindDisplayNames: ImmutableMap<String, String> = persistentMapOf(),
val kindValues: ImmutableMap<String, String> = persistentMapOf(), val kindValues: ImmutableMap<String, String> = persistentMapOf(),
val loadingKinds: Boolean = false, val loadingKinds: Boolean = false
val listItems: ImmutableList<ExploreListItem> = persistentListOf()
) : ListUiState<BookSourcePart> ) : ListUiState<BookSourcePart>
private fun buildExploreListItems(state: ExploreUiState): ImmutableList<ExploreListItem> { fun buildExploreListItems(state: ExploreUiState): ImmutableList<ExploreListItem> {
if (state.items.isEmpty()) return persistentListOf() if (state.items.isEmpty()) return persistentListOf()
val expandedId = state.expandedId val expandedId = state.expandedId
val kindRows = if (expandedId != null) { val kindRows = if (expandedId != null) {
@@ -37,7 +37,7 @@ import io.legado.app.ui.main.homepage.manage.SetListPage
import io.legado.app.ui.main.homepage.manage.SourceBrowseDetailPage import io.legado.app.ui.main.homepage.manage.SourceBrowseDetailPage
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.icon.AppIcon 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.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -127,38 +127,38 @@ fun <T> HomepageModuleManageSheet(
}, },
startAction = { startAction = {
if (showCustomSetAddModules) { if (showCustomSetAddModules) {
SmallIconButton( SmallPlainButton(
onClick = { showCustomSetAddModules = false }, onClick = { showCustomSetAddModules = false },
imageVector = Icons.AutoMirrored.Filled.ArrowBack icon = Icons.AutoMirrored.Filled.ArrowBack
) )
} else if (browsingSourceUrl != null || showSourceBrowser) { } else if (browsingSourceUrl != null || showSourceBrowser) {
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
if (browsingDetail) browsingDetail = false if (browsingDetail) browsingDetail = false
else if (showSourceBrowser) showSourceBrowser = false else if (showSourceBrowser) showSourceBrowser = false
else browsingSourceUrl = null else browsingSourceUrl = null
}, },
imageVector = Icons.AutoMirrored.Filled.ArrowBack icon = Icons.AutoMirrored.Filled.ArrowBack
) )
} else if (selectingSetUrl != null) { } else if (selectingSetUrl != null) {
SmallIconButton( SmallPlainButton(
onClick = { selectingSetUrl = null }, onClick = { selectingSetUrl = null },
imageVector = Icons.AutoMirrored.Filled.ArrowBack icon = Icons.AutoMirrored.Filled.ArrowBack
) )
} }
}, },
endAction = { endAction = {
if (browsingDetail && browseTab == 2 && browseModuleType == "buttonGroup" && selectedKindTitles.isNotEmpty()) { if (browsingDetail && browseTab == 2 && browseModuleType == "buttonGroup" && selectedKindTitles.isNotEmpty()) {
SmallIconButton( SmallPlainButton(
onClick = { showAddButtonGroupDialog = true }, onClick = { showAddButtonGroupDialog = true },
imageVector = Icons.Default.Check icon = Icons.Default.Check
) )
} else if ((showSourceBrowser || browsingSourceUrl != null) && !browsingDetail) { } else if ((showSourceBrowser || browsingSourceUrl != null) && !browsingDetail) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
Box { Box {
SmallIconButton( SmallPlainButton(
onClick = { expanded = true }, onClick = { expanded = true },
imageVector = Icons.Default.FilterList icon = Icons.Default.FilterList
) )
RoundDropdownMenu( RoundDropdownMenu(
expanded = expanded, expanded = expanded,
@@ -72,7 +72,7 @@ import io.legado.app.ui.widget.components.LoadMoreFooter
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.book.SearchBookGridItem import io.legado.app.ui.widget.components.book.SearchBookGridItem
import io.legado.app.ui.widget.components.book.SearchBookPreviewSheet import io.legado.app.ui.widget.components.book.SearchBookPreviewSheet
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
@@ -82,6 +82,7 @@ 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.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarActionButton import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.utils.sendToClip import io.legado.app.utils.sendToClip
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -132,7 +133,7 @@ fun HomepageScreen(
} }
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
viewModel.effects.collect { effect -> viewModel.effects.collectLatest { effect ->
when (effect) { when (effect) {
is HomepageEffect.NavigateToBookInfo -> is HomepageEffect.NavigateToBookInfo ->
onBookClick( onBookClick(
@@ -717,9 +718,9 @@ private fun ModuleHeader(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
if (onNavigate != null) { if (onNavigate != null) {
SmallTonalIconButton( SmallTonalButton(
onClick = onNavigate, onClick = onNavigate,
imageVector = Icons.AutoMirrored.Filled.ArrowForward icon = Icons.AutoMirrored.Filled.ArrowForward
) )
} }
} }
@@ -338,6 +338,12 @@ class HomepageViewModel(
} }
} }
override fun onCleared() {
super.onCleared()
loadJobs.values.forEach { it.cancel() }
loadJobs.clear()
}
private suspend fun syncModulesFromSource(source: BookSource) { private suspend fun syncModulesFromSource(source: BookSource) {
val json = source.homepageModules ?: return val json = source.homepageModules ?: return
ensureSetForSource(source.bookSourceUrl, source.bookSourceName) ensureSetForSource(source.bookSourceUrl, source.bookSourceName)
@@ -26,7 +26,7 @@ import io.legado.app.ui.main.homepage.HomepageModuleManageUi
import io.legado.app.ui.main.homepage.HomepageViewModel import io.legado.app.ui.main.homepage.HomepageViewModel
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SecondaryButton import io.legado.app.ui.widget.components.button.SecondaryButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem 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.SelectionItemCard
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
@@ -129,13 +129,13 @@ fun SetDetailPage(
} }
}, },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { onEditModule(module) }, onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
SmallIconButton( SmallPlainButton(
onClick = { onRequestDeleteModule(module.id) }, onClick = { onRequestDeleteModule(module.id) },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
) )
@@ -160,13 +160,13 @@ fun SetDetailPage(
containerColor = if (isEffective) LegadoTheme.colorScheme.surfaceContainerHigh else LegadoTheme.colorScheme.onSheetContent, containerColor = if (isEffective) LegadoTheme.colorScheme.surfaceContainerHigh else LegadoTheme.colorScheme.onSheetContent,
onEnabledChange = { onToggleModule(module.id, it) }, onEnabledChange = { onToggleModule(module.id, it) },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { onEditModule(module) }, onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
SmallIconButton( SmallPlainButton(
onClick = { onRequestDeleteModule(module.id) }, onClick = { onRequestDeleteModule(module.id) },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
) )
@@ -22,7 +22,7 @@ import io.legado.app.R
import io.legado.app.ui.main.homepage.HomepageSourceManageUi import io.legado.app.ui.main.homepage.HomepageSourceManageUi
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SecondaryButton import io.legado.app.ui.widget.components.button.SecondaryButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.utils.move import io.legado.app.utils.move
@@ -85,13 +85,13 @@ fun SetListPage(
} }
}, },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { onRenameSet(set.sourceUrl) }, onClick = { onRenameSet(set.sourceUrl) },
imageVector = Icons.Default.DriveFileRenameOutline icon = Icons.Default.DriveFileRenameOutline
) )
SmallIconButton( SmallPlainButton(
onClick = { onDeleteSet(set.sourceUrl) }, onClick = { onDeleteSet(set.sourceUrl) },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
) )
@@ -32,7 +32,7 @@ import io.legado.app.ui.main.homepage.HomepageModuleManageUi
import io.legado.app.ui.main.homepage.HomepageViewModel import io.legado.app.ui.main.homepage.HomepageViewModel
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SecondaryButton import io.legado.app.ui.widget.components.button.SecondaryButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.GlassCard 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.ReorderableSelectionItem
import io.legado.app.ui.widget.components.card.SelectionItemCard import io.legado.app.ui.widget.components.card.SelectionItemCard
@@ -170,13 +170,13 @@ fun SourceBrowseDetailPage(
} }
}, },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { onEditModule(module) }, onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
SmallIconButton( SmallPlainButton(
onClick = { onRequestDeleteModule(module.id) }, onClick = { onRequestDeleteModule(module.id) },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
) )
@@ -212,13 +212,13 @@ fun SourceBrowseDetailPage(
containerColor = if (isEffective) LegadoTheme.colorScheme.surfaceContainerHigh else LegadoTheme.colorScheme.onSheetContent, containerColor = if (isEffective) LegadoTheme.colorScheme.surfaceContainerHigh else LegadoTheme.colorScheme.onSheetContent,
onEnabledChange = { onToggleModule(module.id, it) }, onEnabledChange = { onToggleModule(module.id, it) },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { onEditModule(module) }, onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit icon = Icons.Default.Edit
) )
SmallIconButton( SmallPlainButton(
onClick = { onRequestDeleteModule(module.id) }, onClick = { onRequestDeleteModule(module.id) },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
) )
@@ -334,9 +334,9 @@ fun SourceBrowseDetailPage(
onToggleSelection = { showKindSelect = true }, onToggleSelection = { showKindSelect = true },
trailingAction = { trailingAction = {
if (isButtonGroup && selectedKindTitles.isNotEmpty()) { if (isButtonGroup && selectedKindTitles.isNotEmpty()) {
SmallIconButton( SmallPlainButton(
onClick = { onShowAddButtonGroupDialog() }, onClick = { onShowAddButtonGroupDialog() },
imageVector = Icons.Default.Check icon = Icons.Default.Check
) )
} }
} }
@@ -35,8 +35,6 @@ import androidx.compose.material.icons.filled.Source
import androidx.compose.material.icons.filled.Web import androidx.compose.material.icons.filled.Web
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -54,11 +52,12 @@ import io.legado.app.ui.replace.ReplaceRuleActivity
import io.legado.app.ui.theme.adaptiveContentPadding import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.SmallTextButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar 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.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -81,7 +80,7 @@ fun MyScreen(
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = stringResource(R.string.my), title = stringResource(R.string.my),
actions = { actions = {
IconButton( TopBarActionButton(
onClick = { onClick = {
onNavigate( onNavigate(
PrefClickEvent.ShowMd( PrefClickEvent.ShowMd(
@@ -89,10 +88,10 @@ fun MyScreen(
path = "appHelp" path = "appHelp"
) )
) )
} },
) {Icon( imageVector = Icons.AutoMirrored.Filled.HelpOutline,
Icons.AutoMirrored.Filled.HelpOutline, null) contentDescription = null
} )
}, },
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
@@ -251,22 +250,22 @@ fun WebServiceSettingBlock(
.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp), .padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
SmallTextButton( SmallPlainButton(
text = stringResource(R.string.copy_url),
imageVector = Icons.Default.ContentCopy,
onClick = { onClick = {
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress)) onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
} },
icon = Icons.Default.ContentCopy,
text = stringResource(R.string.copy_url)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
SmallTextButton( SmallPlainButton(
text = stringResource(R.string.open_in_browser),
imageVector = Icons.Default.OpenInBrowser,
onClick = { onClick = {
onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress)) onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
} },
icon = Icons.Default.OpenInBrowser,
text = stringResource(R.string.open_in_browser)
) )
} }
} }
@@ -5,8 +5,6 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -19,71 +17,54 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip import androidx.compose.material3.AssistChip
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.ui.res.stringResource
import io.legado.app.R
import io.legado.app.ui.widget.components.AppFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.animateFloatingActionButton import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import io.legado.app.R
import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.button.ToggleChip import io.legado.app.ui.widget.components.button.ToggleChip
import io.legado.app.ui.widget.components.topbar.TopBarActionButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
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.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText 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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@Composable @Composable
@@ -369,9 +350,9 @@ fun GroupSelector(
} }
} }
} }
MediumIconButton( MediumPlainButton(
onClick = onManageClick, onClick = onManageClick,
imageVector = Icons.Default.Settings icon = Icons.Default.Settings
) )
} }
} }
@@ -1,7 +1,6 @@
package io.legado.app.ui.rss.article package io.legado.app.ui.rss.article
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -11,13 +10,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
import androidx.compose.material.icons.automirrored.filled.Login import androidx.compose.material.icons.automirrored.filled.Login
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
@@ -25,7 +22,6 @@ import androidx.compose.material.icons.filled.CleaningServices
import androidx.compose.material.icons.filled.Dataset import androidx.compose.material.icons.filled.Dataset
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Style import androidx.compose.material.icons.filled.Style
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -39,6 +35,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -51,10 +48,9 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton import io.legado.app.ui.widget.components.button.series.SmallToggleButton
import io.legado.app.ui.widget.components.topbar.TopBarActionButton import io.legado.app.ui.widget.components.button.series.ToggleStyle
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
@@ -65,6 +61,8 @@ 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.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar 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.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -242,10 +240,11 @@ fun RssSortScreen(
if (BookshelfConfig.shouldShowExpandButton) { if (BookshelfConfig.shouldShowExpandButton) {
Box { Box {
SmallOutlinedIconToggleButton( SmallToggleButton(
checked = showGroupMenu, checked = showGroupMenu,
onCheckedChange = { showGroupMenu = it }, onCheckedChange = { showGroupMenu = it },
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted, style = ToggleStyle.Outlined,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.group_manage) contentDescription = stringResource(R.string.group_manage)
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -321,9 +320,9 @@ private fun RssReadRecordSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.read_record), title = stringResource(R.string.read_record),
endAction = { endAction = {
SmallIconButton( SmallPlainButton(
onClick = onClear, onClick = onClear,
imageVector = Icons.Default.CleaningServices, icon = Icons.Default.CleaningServices,
contentDescription = stringResource(R.string.clear) contentDescription = stringResource(R.string.clear)
) )
} }
@@ -33,7 +33,7 @@ import io.legado.app.data.entities.RssStar
import io.legado.app.ui.theme.adaptiveContentPadding import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.ActionItem import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.SelectionItemCard 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.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
@@ -229,9 +229,9 @@ fun RssFavoritesScreen(
val openAction = { val openAction = {
onOpenRead(rssStar.title, rssStar.origin, rssStar.link, null) onOpenRead(rssStar.title, rssStar.origin, rssStar.link, null)
} }
SmallIconButton( SmallPlainButton(
onClick = openAction, onClick = openAction,
imageVector = Icons.AutoMirrored.Filled.OpenInNew, icon = Icons.AutoMirrored.Filled.OpenInNew,
contentDescription = "Open" contentDescription = "Open"
) )
}, },
@@ -58,7 +58,7 @@ import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu 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.menuItem.RoundDropdownMenuItem
@@ -422,9 +422,9 @@ private fun FavoriteEditSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.favorite), title = stringResource(R.string.favorite),
endAction = { endAction = {
SmallIconButton( SmallPlainButton(
onClick = onDelete, onClick = onDelete,
imageVector = Icons.Default.CleaningServices, icon = Icons.Default.CleaningServices,
contentDescription = stringResource(R.string.delete) contentDescription = stringResource(R.string.delete)
) )
} }
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@@ -13,13 +12,10 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -45,7 +41,7 @@ import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.DraggableSelectionHandler import io.legado.app.ui.widget.components.DraggableSelectionHandler
import io.legado.app.ui.widget.components.GroupManageBottomSheet import io.legado.app.ui.widget.components.GroupManageBottomSheet
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.dialog.TextListInputDialog 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.divider.PillDivider
@@ -364,9 +360,9 @@ fun RssSourceScreen(
onEnabledChange = { enabled -> viewModel.update(item.source.copy(enabled = enabled)) }, onEnabledChange = { enabled -> viewModel.update(item.source.copy(enabled = enabled)) },
onClickEdit = { onEditSource(item.source) }, onClickEdit = { onEditSource(item.source) },
trailingAction = { trailingAction = {
SmallIconButton( SmallPlainButton(
onClick = { showDeleteRuleDialog = item.source }, onClick = { showDeleteRuleDialog = item.source },
imageVector = Icons.Default.Delete icon = Icons.Default.Delete
) )
} }
) )
@@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.animateFloatingActionButton import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -46,6 +45,7 @@ import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.SelectionItemCard import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
@@ -165,9 +165,11 @@ fun RuleSubScreen(
} }
}, },
trailingAction = { trailingAction = {
IconButton(onClick = { showEditDialog = ruleSub }) { SmallPlainButton(
Icon(Icons.Default.Edit, contentDescription = "Edit") onClick = { showEditDialog = ruleSub },
} icon = Icons.Default.Edit,
contentDescription = "Edit"
)
}, },
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
@@ -35,6 +35,14 @@ fun Modifier.adaptiveVerticalPadding(): Modifier {
return this.padding(horizontal = horizontal) return this.padding(horizontal = horizontal)
} }
@Composable
fun adaptiveHorizonalPadding(): PaddingValues {
val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp
return PaddingValues(
horizontal = horizontal
)
}
@Composable @Composable
fun adaptiveContentPaddingOnlyVertical( fun adaptiveContentPaddingOnlyVertical(
top: Dp, top: Dp,
@@ -0,0 +1,41 @@
package io.legado.app.ui.widget.components
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Slider as MiuixSlider
@Composable
fun AppSlider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixSlider(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
valueRange = valueRange,
steps = steps,
onValueChangeFinished = onValueChangeFinished
)
} else {
Slider(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
valueRange = valueRange,
steps = steps,
onValueChangeFinished = onValueChangeFinished
)
}
}
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentSize
import io.legado.app.ui.widget.components.progressIndicator.AppContainedLoadingIndicator
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -24,8 +23,9 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.button.SmallTonalTextButton import io.legado.app.ui.widget.components.progressIndicator.AppContainedLoadingIndicator
import io.legado.app.ui.widget.components.text.AnimatedTextLine import io.legado.app.ui.widget.components.text.AnimatedTextLine
@Composable @Composable
@@ -84,10 +84,10 @@ fun EmptyMessage(
if (buttonText != null && onButtonClick != null) { if (buttonText != null && onButtonClick != null) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
SmallTonalTextButton( SmallTonalButton(
onClick = onButtonClick, onClick = onButtonClick,
text = buttonText, text = buttonText,
imageVector = buttonImageVector icon = buttonImageVector
) )
} }
} }
@@ -26,7 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.button.SmallTextButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.SettingItem import io.legado.app.ui.widget.components.settingItem.SettingItem
@@ -118,13 +118,13 @@ private fun GroupItem(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
SmallTextButton( SmallPlainButton(
text = stringResource(id = R.string.ok),
imageVector = Icons.Default.Check,
onClick = { onClick = {
onUpdateGroup(group, state.text.toString()) onUpdateGroup(group, state.text.toString())
expanded = false expanded = false
} },
icon = Icons.Default.Check,
text = stringResource(id = R.string.ok)
) )
} }
} }
@@ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonParser import com.google.gson.JsonParser
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
@@ -37,17 +37,17 @@ fun JsonRawEditor(
style = LegadoTheme.typography.labelMediumEmphasized style = LegadoTheme.typography.labelMediumEmphasized
) )
Row { Row {
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
runCatching { runCatching {
val jsonElement = JsonParser.parseString(value) val jsonElement = JsonParser.parseString(value)
onValueChange(GSON.toJson(jsonElement)) onValueChange(GSON.toJson(jsonElement))
} }
}, },
imageVector = Icons.Default.AutoFixHigh, icon = Icons.Default.AutoFixHigh,
contentDescription = "格式化" contentDescription = "格式化"
) )
SmallIconButton( SmallPlainButton(
onClick = { onClick = {
runCatching { runCatching {
val jsonElement = JsonParser.parseString(value) val jsonElement = JsonParser.parseString(value)
@@ -55,7 +55,7 @@ fun JsonRawEditor(
onValueChange(compactGson.toJson(jsonElement)) onValueChange(compactGson.toJson(jsonElement))
} }
}, },
imageVector = Icons.Default.Compress, icon = Icons.Default.Compress,
contentDescription = "压缩" contentDescription = "压缩"
) )
} }
@@ -10,7 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallOutlinedIconButton import io.legado.app.ui.widget.components.button.series.SmallOutlinedButton
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
@Composable @Composable
@@ -26,12 +26,12 @@ fun ValueStepper(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
SmallOutlinedIconButton( SmallOutlinedButton(
onClick = { onClick = {
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange) val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
onValueChange(newValue) onValueChange(newValue)
}, },
imageVector = Icons.Default.Remove icon = Icons.Default.Remove
) )
TextCard( TextCard(
cornerRadius = 8.dp, cornerRadius = 8.dp,
@@ -41,12 +41,12 @@ fun ValueStepper(
backgroundColor = LegadoTheme.colorScheme.surfaceContainer, backgroundColor = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurface contentColor = LegadoTheme.colorScheme.onSurface
) )
SmallOutlinedIconButton( SmallOutlinedButton(
onClick = { onClick = {
val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange) val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
onValueChange(newValue) onValueChange(newValue)
}, },
imageVector = Icons.Default.Add icon = Icons.Default.Add
) )
} }
} }
@@ -1,43 +0,0 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.material3.TextButton
import io.legado.app.ui.widget.components.progressIndicator.AppContainedLoadingIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import io.legado.app.ui.widget.components.text.AppText
@Composable
fun AnimatedTextButton(
isLoading: Boolean,
onClick: () -> Unit,
text: String,
modifier: Modifier = Modifier
) {
TextButton(
onClick = onClick,
enabled = !isLoading,
modifier = modifier.animateContentSize()
) {
AnimatedContent(
targetState = isLoading,
contentAlignment = Alignment.Center,
transitionSpec = {
fadeIn(tween(200)) togetherWith fadeOut(tween(200))
},
label = "ButtonLoading"
) { loading ->
if (loading) {
AppContainedLoadingIndicator()
} else {
AppText(text)
}
}
}
}
@@ -1,217 +0,0 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TonalToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
fun MediumIconButton(
onClick: () -> Unit,
imageVector: ImageVector,
tint: Color = LegadoTheme.colorScheme.onSurface,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription,
tint = tint
)
}
} else {
IconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
tint = tint
)
}
}
}
@Composable
fun MediumOutlinedIconButton(
onClick: () -> Unit,
imageVector: ImageVector,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription
)
}
} else {
OutlinedIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
border = ButtonDefaults.outlinedButtonBorder()
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
tint = LegadoTheme.colorScheme.onSurface
)
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MediumAnimatedActionButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
iconChecked: ImageVector,
iconUnchecked: ImageVector,
activeText: String,
inactiveText: String,
modifier: Modifier = Modifier
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainerHigh,
animationSpec = tween(150),
label = "MiuixActionButtonContainer"
)
val contentColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface,
animationSpec = tween(150),
label = "MiuixActionButtonContent"
)
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked,
iconUnchecked = iconUnchecked,
activeText = activeText,
inactiveText = inactiveText,
modifier = modifier,
iconSize = 24.dp,
textStyle = LegadoTheme.typography.labelMedium,
textStartPadding = 8.dp,
contentColor = contentColor,
button = { buttonModifier, onToggle, content ->
MiuixIconButton(
onClick = { onToggle(!checked) },
modifier = buttonModifier,
backgroundColor = containerColor
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp),
content = content
)
}
},
icon = { imageVector, iconModifier, tint ->
MiuixIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier,
tint = tint ?: Color.Unspecified
)
},
text = { label, textModifier, style, color ->
MiuixText(
text = label,
color = color ?: Color.Unspecified,
style = style,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
} else {
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked,
iconUnchecked = iconUnchecked,
activeText = activeText,
inactiveText = inactiveText,
modifier = modifier,
iconSize = 24.dp,
textStyle = LegadoTheme.typography.labelMedium,
textStartPadding = 8.dp,
button = { buttonModifier, onToggle, content ->
TonalToggleButton(
checked = checked,
onCheckedChange = onToggle,
modifier = buttonModifier,
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = content
)
}
},
icon = { imageVector, iconModifier, _ ->
AnimatedIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier
)
},
text = { label, textModifier, style, color ->
Text(
text = label,
modifier = textModifier,
style = style,
color = color ?: Color.Unspecified,
maxLines = 1,
softWrap = false
)
}
)
}
}
@@ -1,66 +0,0 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
fun MediumOutlinedButton(
onClick: () -> Unit,
imageVector: ImageVector,
text: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription
)
MiuixText(text = text)
}
}
} else {
OutlinedButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
border = ButtonDefaults.outlinedButtonBorder()
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription
)
Text(
text = text,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
@@ -1,356 +0,0 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconToggleButtonShapes
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.Text
import androidx.compose.material3.TonalToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
private val SmallMiuixButtonSize = 32.dp
private val SmallMiuixIconSize = 18.dp
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private fun smallContainerSize() = IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private val smallIconSize: androidx.compose.ui.unit.Dp
get() = IconButtonDefaults.extraSmallIconSize
@Composable
private fun SmallNoMinTouchTarget(content: @Composable () -> Unit) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
content()
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallIconButton(
onClick: () -> Unit,
imageVector: ImageVector,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(16.dp),
)
}
} else {
SmallNoMinTouchTarget {
IconButton(
onClick = onClick,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shape = IconButtonDefaults.extraSmallRoundShape,
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize),
)
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallOutlinedIconButton(
onClick: () -> Unit,
imageVector: ImageVector,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
} else {
SmallNoMinTouchTarget {
OutlinedIconButton(
onClick = onClick,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shapes = IconButtonDefaults.shapes(),
border = ButtonDefaults.outlinedButtonBorder()
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize)
)
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallTonalIconButton(
onClick: () -> Unit,
imageVector: ImageVector,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
} else {
SmallNoMinTouchTarget {
FilledTonalIconButton(
onClick = onClick,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.filledTonalIconButtonColors()
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize)
)
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallOutlinedIconToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
imageVector: ImageVector,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
animationSpec = tween(150),
label = "MiuixToggleContainerColor"
)
val iconTint by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant,
animationSpec = tween(150),
label = "MiuixToggleIconTint"
)
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = containerColor
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription,
tint = iconTint,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
} else {
val defaultShape = IconButtonDefaults.extraSmallRoundShape
val pressedShape = IconButtonDefaults.extraSmallPressedShape
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
val toggleShapes = remember(defaultShape, checkedShape) {
IconToggleButtonShapes(
shape = defaultShape,
pressedShape = pressedShape,
checkedShape = checkedShape
)
}
SmallNoMinTouchTarget {
OutlinedIconToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shapes = toggleShapes
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize),
)
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallAnimatedActionButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
iconChecked: ImageVector,
iconUnchecked: ImageVector,
activeText: String,
inactiveText: String,
modifier: Modifier = Modifier
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainer,
animationSpec = tween(150),
label = "MiuixActionButtonContainer"
)
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked,
iconUnchecked = iconUnchecked,
activeText = activeText,
inactiveText = inactiveText,
modifier = modifier,
iconSize = 18.dp,
textStyle = LegadoTheme.typography.labelSmall,
textStartPadding = 6.dp,
button = { buttonModifier, onToggle, content ->
MiuixIconButton(
onClick = { onToggle(!checked) },
modifier = buttonModifier,
backgroundColor = containerColor
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp),
content = content
)
}
},
icon = { imageVector, iconModifier, tint ->
MiuixIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier,
tint = tint ?: Color.Unspecified
)
},
text = { label, textModifier, style, color ->
MiuixText(
text = label,
style = style,
color = color ?: Color.Unspecified,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
} else {
SmallNoMinTouchTarget {
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked,
iconUnchecked = iconUnchecked,
activeText = activeText,
inactiveText = inactiveText,
modifier = modifier.height(36.dp),
iconSize = 16.dp,
textStyle = LegadoTheme.typography.labelSmall,
textStartPadding = 6.dp,
button = { buttonModifier, onToggle, content ->
TonalToggleButton(
checked = checked,
onCheckedChange = onToggle,
modifier = buttonModifier,
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = content
)
}
},
icon = { imageVector, iconModifier, _ ->
AnimatedIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier
)
},
text = { label, textModifier, style, color ->
Text(
text = label,
style = style,
color = color ?: Color.Unspecified,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
}
}
}
@@ -1,142 +0,0 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
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.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
@Composable
fun SmallTextButton(
text: String,
imageVector: ImageVector,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceVariant,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
Row(
modifier = Modifier
.padding(horizontal = 12.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically
) {
MiuixIcon(
imageVector = imageVector,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
}
}
} else {
TextButton(
onClick = onClick,
modifier = modifier,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
shape = MaterialTheme.shapes.small
) {
Icon(
imageVector = imageVector,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(4.dp))
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
}
}
}
@Composable
fun SmallTonalTextButton(
text: String? = null,
imageVector: ImageVector? = null,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
Row(
modifier = Modifier
.padding(horizontal = 12.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically
) {
if (imageVector != null) {
MiuixIcon(
imageVector = imageVector,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
}
if (text != null) {
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
}
}
}
} else {
FilledTonalButton(
onClick = onClick,
modifier = modifier,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)
) {
if (imageVector != null) {
Icon(
imageVector = imageVector,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
}
Spacer(Modifier.width(4.dp))
if (text != null) {
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
}
}
}
}
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.button package io.legado.app.ui.widget.components.button.series
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.button package io.legado.app.ui.widget.components.button.series
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@@ -0,0 +1,173 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TonalToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MediumAnimatedButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
iconChecked: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainer,
animationSpec = tween(150),
label = "MiuixAnimatedContainerColor"
)
val contentColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface,
animationSpec = tween(150),
label = "MiuixAnimatedContentColor"
)
if (text != null) {
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked ?: icon!!,
iconUnchecked = icon ?: iconChecked!!,
activeText = text,
inactiveText = text,
modifier = modifier,
iconSize = 24.dp,
textStyle = LegadoTheme.typography.labelMedium,
textStartPadding = 8.dp,
contentColor = contentColor,
button = { buttonModifier, onToggle, content ->
MiuixIconButton(
onClick = { onToggle(!checked) },
modifier = buttonModifier,
backgroundColor = containerColor
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp),
content = content
)
}
},
icon = { imageVector, iconModifier, tint ->
MiuixIcon(
tint = tint ?: Color.Unspecified,
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier
)
},
text = { label, textModifier, textStyle, color ->
MiuixText(
text = label,
color = color ?: Color.Unspecified,
style = textStyle,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
} else {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
backgroundColor = containerColor
) {
MiuixIcon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
tint = contentColor
)
}
}
} else {
if (text != null) {
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked ?: icon!!,
iconUnchecked = icon ?: iconChecked!!,
activeText = text,
inactiveText = text,
modifier = modifier.height(36.dp),
iconSize = 20.dp,
textStyle = LegadoTheme.typography.labelMedium,
textStartPadding = 8.dp,
button = { buttonModifier, onToggle, content ->
TonalToggleButton(
modifier = buttonModifier,
contentPadding = PaddingValues(horizontal = 8.dp),
checked = checked,
onCheckedChange = onToggle
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = content
)
}
},
icon = { imageVector, iconModifier, _ ->
AnimatedIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier
)
},
text = { label, textModifier, textStyle, color ->
Text(
text = label,
style = textStyle,
color = color ?: Color.Unspecified,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
} else {
FilledTonalIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
)
}
}
}
}
@@ -0,0 +1,78 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@Composable
fun MediumOutlinedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (icon != null && text == null) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceContainerHigh,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
MediumButtonContent(icon, text, contentDescription)
}
}
} else {
if (icon != null && text == null) {
OutlinedIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
border = ButtonDefaults.outlinedButtonBorder()
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = LegadoTheme.colorScheme.onSurface
)
}
} else {
OutlinedButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
border = ButtonDefaults.outlinedButtonBorder()
) {
MediumButtonContent(icon, text, contentDescription)
}
}
}
}
@@ -0,0 +1,121 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
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.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
internal fun MediumButtonContent(
icon: ImageVector?,
text: String?,
contentDescription: String?
) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null) {
if (isMiuix) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription
)
} else {
Icon(
imageVector = icon,
contentDescription = contentDescription
)
}
}
if (text != null) {
if (isMiuix) {
MiuixText(text = text)
} else {
AppText(text = text)
}
}
}
}
@Composable
fun MediumPlainButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
text: String? = null,
tint: androidx.compose.ui.graphics.Color = LegadoTheme.colorScheme.onSurface,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (icon != null && text == null) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
tint = tint
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceVariant,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
MediumButtonContent(icon, text, contentDescription)
}
}
} else {
if (icon != null && text == null) {
IconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = tint
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceVariant,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
MediumButtonContent(icon, text, contentDescription)
}
}
}
}
@@ -0,0 +1,152 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MediumToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
style: ToggleStyle = ToggleStyle.Outlined,
icon: ImageVector? = null,
iconChecked: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainerHigh,
animationSpec = tween(150),
label = "MiuixToggleContainerColor"
)
val contentColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface,
animationSpec = tween(150),
label = "MiuixToggleContentColor"
)
if (text != null) {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
backgroundColor = containerColor
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp)
) {
MiuixIcon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = null,
tint = contentColor
)
MiuixText(
text = text,
color = contentColor,
style = LegadoTheme.typography.labelMedium,
modifier = Modifier.padding(start = 8.dp),
maxLines = 1,
softWrap = false
)
}
}
} else {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
backgroundColor = containerColor
) {
MiuixIcon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
tint = contentColor
)
}
}
} else {
if (text != null) {
ToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled,
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = null
)
Text(
text = text,
style = LegadoTheme.typography.labelMedium,
modifier = Modifier.padding(start = 8.dp),
maxLines = 1,
softWrap = false
)
}
}
} else {
when (style) {
ToggleStyle.Outlined -> {
FilledTonalButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
)
}
}
ToggleStyle.Tonal -> {
FilledTonalIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
)
}
}
}
}
}
}
@@ -0,0 +1,74 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@Composable
fun MediumTonalButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (icon != null && text == null) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
MediumButtonContent(icon, text, contentDescription)
}
}
} else {
if (icon != null && text == null) {
FilledTonalIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
Icon(
imageVector = icon,
contentDescription = contentDescription
)
}
} else {
FilledTonalButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
MediumButtonContent(icon, text, contentDescription)
}
}
}
}
@@ -0,0 +1,176 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.TonalToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallAnimatedButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
iconChecked: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
animationSpec = tween(150),
label = "MiuixAnimatedContainerColor"
)
val iconTint by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant,
animationSpec = tween(150),
label = "MiuixAnimatedIconTint"
)
if (text != null) {
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked ?: icon!!,
iconUnchecked = icon ?: iconChecked!!,
activeText = text,
inactiveText = text,
modifier = modifier,
iconSize = 18.dp,
textStyle = LegadoTheme.typography.labelSmall,
textStartPadding = 6.dp,
contentColor = iconTint,
button = { buttonModifier, onToggle, content ->
MiuixIconButton(
onClick = { onToggle(!checked) },
modifier = buttonModifier,
backgroundColor = containerColor
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp),
content = content
)
}
},
icon = { imageVector, iconModifier, tint ->
MiuixIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier,
tint = tint ?: Color.Unspecified
)
},
text = { label, textModifier, textStyle, color ->
MiuixText(
text = label,
style = textStyle,
color = color ?: Color.Unspecified,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
} else {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = containerColor
) {
MiuixIcon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
tint = iconTint,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
}
} else {
SmallNoMinTouchTarget {
if (text != null) {
AnimatedActionButtonCore(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked ?: icon!!,
iconUnchecked = icon ?: iconChecked!!,
activeText = text,
inactiveText = text,
modifier = modifier.height(36.dp),
iconSize = 16.dp,
textStyle = LegadoTheme.typography.labelSmall,
textStartPadding = 6.dp,
button = { buttonModifier, onToggle, content ->
TonalToggleButton(
checked = checked,
onCheckedChange = onToggle,
modifier = buttonModifier,
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = content
)
}
},
icon = { imageVector, iconModifier, _ ->
AnimatedIcon(
imageVector = imageVector,
contentDescription = null,
modifier = iconModifier
)
},
text = { label, textModifier, textStyle, color ->
androidx.compose.material3.Text(
text = label,
style = textStyle,
color = color ?: Color.Unspecified,
modifier = textModifier,
maxLines = 1,
softWrap = false
)
}
)
} else {
FilledTonalIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize),
)
}
}
}
}
}
@@ -0,0 +1,93 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallOutlinedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (icon != null && text == null) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
SmallButtonContent(icon, text, contentDescription)
}
}
} else {
SmallNoMinTouchTarget {
when {
icon != null && text == null -> {
OutlinedIconButton(
onClick = onClick,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shapes = IconButtonDefaults.shapes(),
border = ButtonDefaults.outlinedButtonBorder()
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize)
)
}
}
else -> {
OutlinedButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
border = ButtonDefaults.outlinedButtonBorder()
) {
SmallButtonContent(icon, text, contentDescription)
}
}
}
}
}
}
@@ -0,0 +1,161 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
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.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
internal val SmallMiuixButtonSize = 32.dp
internal val SmallMiuixIconSize = 18.dp
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal fun smallContainerSize() = IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal val smallIconSize: Dp
get() = IconButtonDefaults.extraSmallIconSize
@Composable
internal fun SmallNoMinTouchTarget(content: @Composable () -> Unit) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
content()
}
}
@Composable
internal fun SmallButtonContent(
icon: ImageVector?,
text: String?,
contentDescription: String?
) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null) {
if (isMiuix) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(16.dp)
)
} else {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(16.dp)
)
}
}
if (text != null) {
if (isMiuix) {
MiuixText(
text = text,
style = LegadoTheme.typography.labelMedium
)
} else {
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
}
}
}
}
@Composable
fun SmallPlainButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (icon != null && text == null) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize),
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceVariant,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
SmallButtonContent(icon, text, contentDescription)
}
}
} else {
SmallNoMinTouchTarget {
when {
icon != null && text == null -> {
IconButton(
onClick = onClick,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shape = IconButtonDefaults.extraSmallRoundShape,
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize),
)
}
}
else -> {
TextButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
shape = MaterialTheme.shapes.small
) {
SmallButtonContent(icon, text, contentDescription)
}
}
}
}
}
}
@@ -0,0 +1,178 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconToggleButtonShapes
import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.Text
import androidx.compose.material3.TonalToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
enum class ToggleStyle { Outlined, Tonal }
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
style: ToggleStyle = ToggleStyle.Outlined,
icon: ImageVector? = null,
iconChecked: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
animationSpec = tween(150),
label = "MiuixToggleContainerColor"
)
val iconTint by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant,
animationSpec = tween(150),
label = "MiuixToggleIconTint"
)
if (text != null) {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier,
enabled = enabled,
backgroundColor = containerColor
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp)
) {
MiuixIcon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = iconTint
)
MiuixText(
text = text,
style = LegadoTheme.typography.labelSmall,
color = iconTint,
modifier = Modifier.padding(start = 6.dp),
maxLines = 1,
softWrap = false
)
}
}
} else {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = containerColor
) {
MiuixIcon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
tint = iconTint,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
}
} else {
SmallNoMinTouchTarget {
if (text != null) {
TonalToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled,
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Text(
text = text,
style = LegadoTheme.typography.labelSmall,
modifier = Modifier.padding(start = 6.dp),
maxLines = 1,
softWrap = false
)
}
}
} else {
when (style) {
ToggleStyle.Outlined -> {
val defaultShape = IconButtonDefaults.extraSmallRoundShape
val pressedShape = IconButtonDefaults.extraSmallPressedShape
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
val toggleShapes = remember(defaultShape, checkedShape) {
IconToggleButtonShapes(
shape = defaultShape,
pressedShape = pressedShape,
checkedShape = checkedShape
)
}
OutlinedIconToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shapes = toggleShapes
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize),
)
}
}
ToggleStyle.Tonal -> {
FilledTonalButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
) {
Icon(
imageVector = if (checked) (iconChecked ?: icon)!! else icon!!,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize),
)
}
}
}
}
}
}
}
@@ -0,0 +1,91 @@
package io.legado.app.ui.widget.components.button.series
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.CardDefaults
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SmallTonalButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
icon: ImageVector? = null,
text: String? = null,
contentDescription: String? = null
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (icon != null && text == null) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(SmallMiuixButtonSize),
enabled = enabled,
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize)
)
}
} else {
Card(
onClick = onClick,
modifier = modifier,
showIndication = true,
colors = CardDefaults.defaultColors(
color = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant
)
) {
SmallButtonContent(icon, text, contentDescription)
}
}
} else {
SmallNoMinTouchTarget {
when {
icon != null && text == null -> {
FilledTonalIconButton(
onClick = onClick,
modifier = modifier.size(smallContainerSize()),
enabled = enabled,
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.filledTonalIconButtonColors()
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize)
)
}
}
else -> {
FilledTonalButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)
) {
SmallButtonContent(icon, text, contentDescription)
}
}
}
}
}
}
@@ -16,13 +16,8 @@ import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -42,7 +37,7 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.AdaptiveSwitch import io.legado.app.ui.widget.components.AdaptiveSwitch
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.checkBox.AppCheckbox import io.legado.app.ui.widget.components.checkBox.AppCheckbox
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
@@ -50,7 +45,6 @@ import io.legado.app.ui.widget.components.text.AppText
import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.ReorderableLazyListState import sh.calvin.reorderable.ReorderableLazyListState
import top.yukonga.miuix.kmp.basic.BasicComponent import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable @Composable
fun SelectionItemCard( fun SelectionItemCard(
@@ -218,9 +212,9 @@ fun SelectionItemCardContent(
} }
if (onClickEdit != null) { if (onClickEdit != null) {
SmallIconButton( SmallPlainButton(
onClick = onClickEdit, onClick = onClickEdit,
imageVector = AppIcons.Edit, icon = AppIcons.Edit,
contentDescription = "Edit" contentDescription = "Edit"
) )
} }
@@ -231,9 +225,9 @@ fun SelectionItemCardContent(
if (dropdownContent != null) { if (dropdownContent != null) {
Box { Box {
SmallIconButton( SmallPlainButton(
onClick = { showMenu = true }, onClick = { showMenu = true },
imageVector = AppIcons.MoreVert, icon = AppIcons.MoreVert,
contentDescription = "More" contentDescription = "More"
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -36,8 +36,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.MediumAnimatedActionButton import io.legado.app.ui.widget.components.button.series.MediumOutlinedButton
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton import io.legado.app.ui.widget.components.button.series.MediumToggleButton
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import java.time.LocalDate import java.time.LocalDate
@@ -54,15 +54,14 @@ fun HeatmapCalendarStartAction(
currentMode: HeatmapMode, currentMode: HeatmapMode,
onModeChanged: (HeatmapMode) -> Unit, onModeChanged: (HeatmapMode) -> Unit,
) { ) {
MediumAnimatedActionButton( MediumToggleButton(
checked = currentMode == HeatmapMode.TIME, checked = currentMode == HeatmapMode.TIME,
onCheckedChange = { onCheckedChange = {
onModeChanged(if (it) HeatmapMode.TIME else HeatmapMode.COUNT) onModeChanged(if (it) HeatmapMode.TIME else HeatmapMode.COUNT)
}, },
icon = Icons.Default.FormatListNumbered,
iconChecked = Icons.Default.AccessTime, iconChecked = Icons.Default.AccessTime,
iconUnchecked = Icons.Default.FormatListNumbered, text = "按时长"
activeText = "按时长",
inactiveText = "按次数"
) )
} }
@@ -73,9 +72,9 @@ fun HeatmapCalendarStartAction(
fun HeatmapCalendarEndAction( fun HeatmapCalendarEndAction(
onClearDate: () -> Unit onClearDate: () -> Unit
) { ) {
MediumOutlinedIconButton( MediumOutlinedButton(
onClick = onClearDate, onClick = onClearDate,
imageVector = Icons.Outlined.Delete, icon = Icons.Outlined.Delete,
) )
} }
@@ -44,7 +44,7 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog 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.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.card.GlassCard 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.card.SelectionItemCard
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -183,9 +183,9 @@ fun <T> BatchImportDialog(
title = sheetTitle, title = sheetTitle,
startAction = if (isEditing) { startAction = if (isEditing) {
{ {
SmallIconButton( SmallPlainButton(
onClick = { editingIndex = null }, onClick = { editingIndex = null },
imageVector = Icons.AutoMirrored.Filled.ArrowBack, icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "返回" contentDescription = "返回"
) )
} }
@@ -196,9 +196,9 @@ fun <T> BatchImportDialog(
{ {
Row { Row {
topBarActions() topBarActions()
SmallIconButton( SmallPlainButton(
onClick = { onToggleAll(!allSelected) }, onClick = { onToggleAll(!allSelected) },
imageVector = Icons.Default.SelectAll, icon = Icons.Default.SelectAll,
contentDescription = if (allSelected) "全不选" else "全选" contentDescription = if (allSelected) "全不选" else "全选"
) )
} }
@@ -371,9 +371,9 @@ fun ImportItemRow(
modifier = Modifier.padding(end = 4.dp) modifier = Modifier.padding(end = 4.dp)
) )
SmallIconButton( SmallPlainButton(
onClick = onInfoClick, onClick = onInfoClick,
imageVector = Icons.Default.Info, icon = Icons.Default.Info,
contentDescription = "详情" contentDescription = "详情"
) )
} }
@@ -23,7 +23,7 @@ import io.legado.app.R
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
@@ -43,12 +43,12 @@ fun AppLogSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = stringResource(R.string.log), title = stringResource(R.string.log),
endAction = { endAction = {
MediumIconButton( MediumPlainButton(
onClick = { onClick = {
AppLog.clear() AppLog.clear()
logs = emptyList() logs = emptyList()
}, },
imageVector = Icons.Default.DeleteSweep icon = Icons.Default.DeleteSweep
) )
} }
) { ) {
@@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -65,7 +63,7 @@ fun RowScope.OptionCard(
.weight(1f) .weight(1f)
.height(100.dp), .height(100.dp),
cornerRadius = 12.dp, cornerRadius = 12.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainerLow, containerColor = LegadoTheme.colorScheme.onSheetContent,
elevation = 2.dp elevation = 2.dp
) { ) {
Column( Column(
@@ -13,15 +13,8 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -30,19 +23,16 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppFloatingActionButton import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
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.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -84,17 +74,17 @@ fun <T> RuleEditSheet(
AppModalBottomSheet( AppModalBottomSheet(
title = title, title = title,
startAction = { startAction = {
MediumIconButton( MediumPlainButton(
onClick = onDismissRequest, onClick = onDismissRequest,
imageVector = Icons.Default.Close, icon = Icons.Default.Close,
contentDescription = "Close", contentDescription = "Close",
) )
}, },
endAction = { endAction = {
Box{ Box{
MediumIconButton( MediumPlainButton(
onClick = { showMenu = true }, onClick = { showMenu = true },
imageVector = Icons.Default.MoreVert, icon = Icons.Default.MoreVert,
contentDescription = "More" contentDescription = "More"
) )
RoundDropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { RoundDropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
@@ -1,9 +1,13 @@
package io.legado.app.ui.widget.components.topbar package io.legado.app.ui.widget.components.topbar
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -16,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LocalHazeState import io.legado.app.ui.theme.LocalHazeState
@@ -81,7 +86,9 @@ fun GlassMediumFlexibleTopAppBar(
} }
val finalModifier = if (hazeState != null) { val finalModifier = if (hazeState != null) {
modifier.background(color = animatedColor).responsiveHazeEffect(state = hazeState) modifier
.background(color = animatedColor)
.responsiveHazeEffect(state = hazeState)
} else { } else {
modifier.background(color = animatedColor) modifier.background(color = animatedColor)
} }
@@ -116,7 +123,7 @@ fun GlassMediumFlexibleTopAppBar(
AdaptiveAnimatedText( AdaptiveAnimatedText(
text = title, text = title,
useCharMode = useCharMode, useCharMode = useCharMode,
maxLines = 1, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
}, },
@@ -126,7 +133,13 @@ fun GlassMediumFlexibleTopAppBar(
} }
}, },
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = {
Box(modifier = Modifier.padding(end = 12.dp)) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { actions() }
}
},
scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior, scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
colors = transparentColors colors = transparentColors
) )
@@ -153,7 +166,15 @@ fun GlassMediumFlexibleTopAppBar(
} }
}, },
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = {
Box(modifier = Modifier.padding(end = 12.dp)) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
actions()
}
}
},
scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior, scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
colors = transparentColors colors = transparentColors
) )
@@ -30,13 +30,13 @@ import io.legado.app.R
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.button.AnimatedActionButtonCore import io.legado.app.ui.widget.components.button.series.AnimatedActionButtonCore
import io.legado.app.ui.widget.components.button.AnimatedIcon import io.legado.app.ui.widget.components.button.series.AnimatedIcon
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Text as MiuixText
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
private fun TopBarButton( private fun TopBarButton(
@@ -112,7 +112,7 @@ fun TopBarActionButton(
onClick = onClick, onClick = onClick,
imageVector = imageVector, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = modifier.padding(end = 12.dp) modifier = modifier
) )
} else { } else {
IconButton( IconButton(
@@ -140,16 +140,10 @@ fun TopBarAnimatedActionButton(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) { if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
val containerColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.primaryContainer else MiuixTheme.colorScheme.surfaceContainerHigh,
animationSpec = tween(150),
label = "MiuixActionButtonContainer"
)
val contentColor by animateColorAsState( val contentColor by animateColorAsState(
targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface, targetValue = if (checked) MiuixTheme.colorScheme.primary else MiuixTheme.colorScheme.onSurface,
animationSpec = tween(150), animationSpec = tween(150),
label = "MiuixActionButtonContainer" label = "MiuixActionButtonContent"
) )
AnimatedActionButtonCore( AnimatedActionButtonCore(
@@ -167,8 +161,7 @@ fun TopBarAnimatedActionButton(
button = { buttonModifier, onToggle, content -> button = { buttonModifier, onToggle, content ->
MiuixIconButton( MiuixIconButton(
onClick = { onToggle(!checked) }, onClick = { onToggle(!checked) },
modifier = buttonModifier, modifier = buttonModifier
backgroundColor = containerColor
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -1,24 +1,41 @@
package io.legado.app.web.socket package io.legado.app.web.socket
import io.ktor.server.websocket.* import io.ktor.server.websocket.DefaultWebSocketServerSession
import io.ktor.websocket.* import io.ktor.websocket.CloseReason
import io.ktor.websocket.Frame
import io.ktor.websocket.close
import io.ktor.websocket.readText
import io.ktor.websocket.send
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.data.local.preferences.LocalPreferencesKeys
import io.legado.app.data.local.preferences.LocalPreferencesRepository
import io.legado.app.domain.model.BookSearchScope import io.legado.app.domain.model.BookSearchScope
import io.legado.app.domain.model.MatchMode
import io.legado.app.domain.usecase.BookSearchControl import io.legado.app.domain.usecase.BookSearchControl
import io.legado.app.domain.usecase.BookSearchRequest import io.legado.app.domain.usecase.BookSearchRequest
import io.legado.app.domain.usecase.SearchBooksUseCase import io.legado.app.domain.usecase.SearchBooksUseCase
import io.legado.app.domain.usecase.SearchRunEvent import io.legado.app.domain.usecase.SearchRunEvent
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.otherConfig.OtherConfig import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.utils.* import io.legado.app.utils.GSON
import kotlinx.coroutines.* import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.isJson
import io.legado.app.utils.printOnDebug
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.koin.core.context.GlobalContext import org.koin.core.context.GlobalContext
import splitties.init.appCtx import splitties.init.appCtx
class BookSearchWebSocket(private val session: DefaultWebSocketServerSession) : CoroutineScope by session { class BookSearchWebSocket(private val session: DefaultWebSocketServerSession) : CoroutineScope by session {
private val searchBooksUseCase: SearchBooksUseCase by lazy { GlobalContext.get().get() } private val searchBooksUseCase: SearchBooksUseCase by lazy { GlobalContext.get().get() }
private val localPreferencesRepository: LocalPreferencesRepository by lazy {
GlobalContext.get().get()
}
private val searchControl = BookSearchControl() private val searchControl = BookSearchControl()
private val sentBookUrls = linkedSetOf<String>() private val sentBookUrls = linkedSetOf<String>()
private var searchJob: Job? = null private var searchJob: Job? = null
@@ -66,7 +83,14 @@ class BookSearchWebSocket(private val session: DefaultWebSocketServerSession) :
keyword = key, keyword = key,
page = 1, page = 1,
scope = BookSearchScope(AppConfig.searchScope), scope = BookSearchScope(AppConfig.searchScope),
precision = appCtx.getPrefBoolean(PreferKey.precisionSearch), matchMode = MatchMode.of(
localPreferencesRepository
.getPreference(
LocalPreferencesKeys.MATCH_MODE,
MatchMode.DEFAULT.value
)
.first()
),
concurrency = OtherConfig.threadCount, concurrency = OtherConfig.threadCount,
), ),
searchControl searchControl
+4 -2
View File
@@ -24,7 +24,7 @@
<string name="dialog_setting">去设置</string> <string name="dialog_setting">去设置</string>
<string name="tip_cannot_jump_setting_page">无法跳转至设置界面</string> <string name="tip_cannot_jump_setting_page">无法跳转至设置界面</string>
<string name="manual_input">手动输入</string> <string name="manual_input">输入URL</string>
<string name="preferred_contrast">偏好对比度</string> <string name="preferred_contrast">偏好对比度</string>
<string name="contrast_source_system_summary">来源:系统对比度</string> <string name="contrast_source_system_summary">来源:系统对比度</string>
<string name="contrast_source_manual_summary">来源:手动输入</string> <string name="contrast_source_manual_summary">来源:手动输入</string>
@@ -897,7 +897,7 @@
<string name="body_title">正文标题</string> <string name="body_title">正文标题</string>
<string name="show_hide">显示/隐藏</string> <string name="show_hide">显示/隐藏</string>
<string name="header_footer">页眉与页脚</string> <string name="header_footer">页眉与页脚</string>
<string name="rule_subscription">规则订阅</string> <string name="rule_subscription">规则</string>
<string name="rule_sub_empty_msg">添加大佬们提供的规则导入地址\n添加后点击可导入规则</string> <string name="rule_sub_empty_msg">添加大佬们提供的规则导入地址\n添加后点击可导入规则</string>
<string name="get_book_progress">拉取云端进度</string> <string name="get_book_progress">拉取云端进度</string>
<string name="cover_book_progress">覆盖云端进度</string> <string name="cover_book_progress">覆盖云端进度</string>
@@ -1643,6 +1643,8 @@
<string name="search_source_progress"> · 进度 %1$d/%2$d</string> <string name="search_source_progress"> · 进度 %1$d/%2$d</string>
<string name="search_empty_scope_disable_precision">%1$s分组搜索结果为空,是否关闭精准搜索?</string> <string name="search_empty_scope_disable_precision">%1$s分组搜索结果为空,是否关闭精准搜索?</string>
<string name="search_empty_scope_switch_all">%1$s分组搜索结果为空,是否切换到全部分组?</string> <string name="search_empty_scope_switch_all">%1$s分组搜索结果为空,是否切换到全部分组?</string>
<string name="search_layout_list">列表布局</string>
<string name="search_layout_source_grouped">来源分组</string>
<string name="download_cache_config">下载与缓存</string> <string name="download_cache_config">下载与缓存</string>
<string name="http_cache">HTTP缓存</string> <string name="http_cache">HTTP缓存</string>
<string name="cover_cache">封面缓存</string> <string name="cover_cache">封面缓存</string>
+2
View File
@@ -1679,6 +1679,8 @@
<string name="search_source_progress"> · Progress %1$d/%2$d</string> <string name="search_source_progress"> · Progress %1$d/%2$d</string>
<string name="search_empty_scope_disable_precision">%1$s group search returned no results. Disable precise search?</string> <string name="search_empty_scope_disable_precision">%1$s group search returned no results. Disable precise search?</string>
<string name="search_empty_scope_switch_all">%1$s group search returned no results. Switch to all groups?</string> <string name="search_empty_scope_switch_all">%1$s group search returned no results. Switch to all groups?</string>
<string name="search_layout_list">List layout</string>
<string name="search_layout_source_grouped">Source grouped</string>
<string name="download_cache_config">Download &amp; Cache</string> <string name="download_cache_config">Download &amp; Cache</string>
<string name="http_cache">HTTP Cache</string> <string name="http_cache">HTTP Cache</string>
<string name="cover_cache">Cover Cache</string> <string name="cover_cache">Cover Cache</string>