一些神必优化

This commit is contained in:
HapeLee
2026-05-27 01:00:58 +08:00
parent df2aaff0e2
commit 30fe70ffae
89 changed files with 2278 additions and 1561 deletions
@@ -4,10 +4,13 @@ import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
val Context.localDataStore: DataStore<Preferences> by preferencesDataStore(name = "local_ui_status")
object LocalPreferencesKeys {
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(
bookSource: BookSource,
url: String,
page: Int
page: Int,
key: String?
): 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) {
@@ -5,5 +5,10 @@ import io.legado.app.data.entities.SearchBook
interface ExploreBooksGateway {
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,
moduleUrl: String?,
args: String?,
page: Int = 1
page: Int = 1,
key: String? = null,
): ExploreResult = withContext(Dispatchers.IO) {
val base = gateway.getBookSource(sourceUrl)
?: throw SourceNotFound(sourceUrl)
val source = args?.let { base.copy().also { s -> s.setVariable(it) } } ?: base
val resolvedUrl = moduleUrl ?: source.exploreUrl
?: throw NoExploreUrl(sourceUrl)
val books = gateway.exploreBooks(source, resolvedUrl, page)
val resolvedUrl = moduleUrl
?: (if (key != null) source.searchUrl else null)
?: source.exploreUrl
?: throw NoExploreUrl(sourceUrl)
val books = gateway.exploreBooks(source, resolvedUrl, page, key = key)
ExploreResult(resolvedUrl, books)
}
@@ -109,9 +109,12 @@ object WebBook {
url: String,
page: Int? = 1,
ruleData: RuleDataInterface = RuleData(),
key: String? = null,
isSearch: Boolean = false,
): ArrayList<SearchBook> {
val analyzeUrl = AnalyzeUrl(
mUrl = url,
key = key,
page = page,
baseUrl = bookSource.bookSourceUrl,
source = bookSource,
@@ -137,7 +140,7 @@ object WebBook {
analyzeUrl = analyzeUrl,
baseUrl = res.url,
body = res.body,
isSearch = false
isSearch = isSearch
)
}
@@ -146,8 +149,10 @@ object WebBook {
url: String,
page: Int? = 1,
ruleData: RuleDataInterface = RuleData(),
key: String? = null,
isSearch: Boolean = false,
): List<SearchBook> {
return exploreBookAwait(bookSource, url, page, ruleData)
return exploreBookAwait(bookSource, url, page, ruleData, key, isSearch)
}
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.AppScaffold
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.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon
@@ -347,7 +347,7 @@ private fun BookCacheBookCard(
color = LegadoTheme.colorScheme.onSurfaceVariant
)
if (item.hasDownloadTask || item.cachedCount < item.totalCount) {
SmallTonalIconButton(
SmallTonalButton(
onClick = {
if (item.hasActiveDownload) {
onIntent(BookCacheManageIntent.StopBookDownload(item.bookUrl))
@@ -355,7 +355,7 @@ private fun BookCacheBookCard(
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 {
item.hasActiveDownload -> "暂停本书下载"
item.isPaused -> "继续本书下载"
@@ -363,9 +363,9 @@ private fun BookCacheBookCard(
}
)
}
SmallTonalIconButton(
SmallTonalButton(
onClick = { onDeleteBook(item) },
imageVector = Icons.Default.Delete,
icon = Icons.Default.Delete,
contentDescription = null
)
}
@@ -412,21 +412,21 @@ private fun BookCacheChapterRow(
)
}
if (item.isWaiting || item.isDownloading) {
SmallTonalIconButton(
SmallTonalButton(
onClick = onStop,
imageVector = Icons.Default.Stop,
icon = Icons.Default.Stop,
contentDescription = "暂停章节下载"
)
} else if (item.isPaused || !item.isCached) {
SmallTonalIconButton(
SmallTonalButton(
onClick = onDownload,
imageVector = Icons.Default.Download,
icon = Icons.Default.Download,
contentDescription = if (item.isPaused) "继续章节下载" else "下载章节"
)
}
SmallTonalIconButton(
SmallTonalButton(
onClick = onDelete,
imageVector = Icons.Default.Delete,
icon = Icons.Default.Delete,
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.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
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.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import io.legado.app.data.entities.SearchBook
import io.legado.app.domain.model.BookShelfState
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.main.bookCoverSharedElementKey
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.responsiveHazeSource
import io.legado.app.ui.widget.components.AppPullToRefresh
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.book.SearchBookGridItem
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.TopBarNavigationButton
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")
@OptIn(
@@ -83,55 +89,51 @@ import org.koin.compose.koinInject
)
@Composable
fun ExploreShowScreen(
title: String,
sourceUrl: String?,
exploreUrl: String?,
viewModel: ExploreShowViewModel = koinViewModel(),
title: String = "",
onBack: () -> Unit,
onBookClick: (SearchBook, String?) -> Unit,
viewModel: ExploreShowViewModel = koinViewModel(),
sharedTransitionScope: SharedTransitionScope? = 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 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 books = remember(rawBooks, filterStateId) {
val books = remember(state.books, filterStateId) {
val filter = BookFilterState.fromId(filterStateId)
when (filter) {
BookFilterState.SHOW_ALL -> rawBooks
BookFilterState.HIDE_IN_SHELF -> rawBooks.filter { it.shelfState != BookShelfState.IN_SHELF }
BookFilterState.HIDE_SAME_NAME_AUTHOR -> rawBooks.filter { it.shelfState != BookShelfState.SAME_NAME_AUTHOR }
BookFilterState.SHOW_NOT_IN_SHELF_ONLY -> rawBooks.filter { it.shelfState == BookShelfState.NOT_IN_SHELF }
BookFilterState.SHOW_ALL -> state.books
BookFilterState.HIDE_IN_SHELF -> state.books.filter { it.shelfState != BookShelfState.IN_SHELF }
BookFilterState.HIDE_SAME_NAME_AUTHOR -> state.books.filter { it.shelfState != BookShelfState.SAME_NAME_AUTHOR }
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 gridState = rememberLazyGridState()
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
var showKindSheet by remember { mutableStateOf(false) }
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 isGridMode = state.layoutState == 1
val hazeState = remember { HazeState() }
val shouldLoadMoreList = remember {
derivedStateOf {
@@ -150,17 +152,11 @@ fun ExploreShowScreen(
}
LaunchedEffect(shouldLoadMoreList.value, isGridMode) {
if (!isGridMode && shouldLoadMoreList.value) viewModel.loadMore()
if (!isGridMode && shouldLoadMoreList.value) viewModel.onIntent(ExploreShowIntent.LoadMore)
}
LaunchedEffect(shouldLoadMoreGrid.value, isGridMode) {
if (isGridMode && shouldLoadMoreGrid.value) viewModel.loadMore()
}
LaunchedEffect(shouldTriggerAutoLoad) {
if (shouldTriggerAutoLoad) {
viewModel.loadMore()
}
if (isGridMode && shouldLoadMoreGrid.value) viewModel.onIntent(ExploreShowIntent.LoadMore)
}
LaunchedEffect(isGridMode) {
@@ -175,17 +171,14 @@ fun ExploreShowScreen(
}
}
AppModalBottomSheet(
show = showGridCountSheet,
modifier = Modifier
.padding(16.dp),
onDismissRequest = { showGridCountSheet = false }
show = state.sheet == ExploreShowSheet.GridCount,
onDismissRequest = { viewModel.onIntent(ExploreShowIntent.DismissSheet) }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
.padding(bottom = 32.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
@@ -193,49 +186,38 @@ fun ExploreShowScreen(
text = "布局列数",
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
)
Spacer(modifier = Modifier.width(12.dp))
TextCard(
text = "$gridColumnCount",
text = "${state.gridCount}",
textStyle = LegadoTheme.typography.titleSmall,
backgroundColor = LegadoTheme.colorScheme.onSheetContent,
verticalPadding = 4.dp,
horizontalPadding = 12.dp,
cornerRadius = 12.dp
)
}
Slider(
value = gridColumnCount.toFloat(),
AppSlider(
value = state.gridCount.toFloat(),
onValueChange = {
val col = it.toInt().coerceIn(1, 10)
viewModel.saveGridCount(col)
viewModel.onIntent(ExploreShowIntent.SaveGridCount(it.toInt().coerceIn(1, 10)))
},
valueRange = 1f..10f,
steps = 8,
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(
show = showKindSheet,
onDismissRequest = { showKindSheet = false },
sourceUrl = sourceUrl,
show = state.sheet == ExploreShowSheet.KindSelect,
onDismissRequest = { viewModel.onIntent(ExploreShowIntent.DismissSheet) },
sourceUrl = state.sourceUrl,
onSelected = { selectedKinds ->
selectedKinds.firstOrNull()?.let { kind ->
viewModel.switchExploreUrl(kind)
viewModel.onIntent(ExploreShowIntent.SwitchKind(kind))
}
}
)
@@ -245,39 +227,39 @@ fun ExploreShowScreen(
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
modifier = Modifier.responsiveHazeEffect(
state = hazeState
),
title = selectedTitle ?: title,
modifier = Modifier.responsiveHazeEffect(state = hazeState),
title = state.selectedKindTitle ?: title,
navigationIcon = {
TopBarNavigationButton(onClick = onBack)
},
actions = {
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.animateContentSize(tween(300))
AnimatedVisibility(
visible = isGridMode,
enter = fadeIn(tween(300)),
exit = fadeOut(tween(300))
) {
TopBarActionButton(
onClick = { showKindSheet = true },
imageVector = Icons.Outlined.FilterAlt,
contentDescription = "分类"
onClick = {
viewModel.onIntent(
ExploreShowIntent.ShowSheet(
ExploreShowSheet.GridCount
)
)
},
imageVector = Icons.AutoMirrored.Outlined.FormatListBulleted,
contentDescription = "列数设置"
)
AnimatedVisibility(
visible = isGridMode,
enter = fadeIn(tween(300)),
exit = fadeOut(tween(300))
) {
TopBarActionButton(
onClick = { showGridCountSheet = true },
imageVector = Icons.AutoMirrored.Outlined.FormatListBulleted,
contentDescription = "列数设置"
)
}
}
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,
contentDescription = "切换布局"
)
@@ -288,8 +270,8 @@ fun ExploreShowScreen(
) { paddingValues ->
AppPullToRefresh(
modifier = Modifier.fillMaxSize(),
isRefreshing = isRefreshing,
onRefresh = { viewModel.loadMore(isRefresh = true) },
isRefreshing = state.isRefreshing,
onRefresh = { viewModel.onIntent(ExploreShowIntent.Refresh) },
topPadding = paddingValues.calculateTopPadding()
) {
Crossfade(
@@ -303,7 +285,7 @@ fun ExploreShowScreen(
modifier = Modifier
.fillMaxSize()
.responsiveHazeSource(hazeState),
columns = GridCells.Fixed(gridColumnCount),
columns = GridCells.Fixed(state.gridCount),
contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding() + 12.dp,
bottom = paddingValues.calculateBottomPadding() + 12.dp,
@@ -324,7 +306,14 @@ fun ExploreShowScreen(
ExploreBookGridItem(
book = item.book,
shelfState = item.shelfState,
onClick = { onBookClick(item.book, sharedCoverKey) },
onClick = {
viewModel.onIntent(
ExploreShowIntent.OpenBook(
item.book,
sharedCoverKey
)
)
},
onLongClick = { book, coverKey ->
previewBook = book
previewSharedCoverKey = coverKey
@@ -338,10 +327,10 @@ fun ExploreShowScreen(
item(span = { GridItemSpan(maxLineSpan) }) {
LoadMoreFooter(
isLoading = isLoading,
errorMsg = errorMsg,
isEnd = isBookEnd,
onRetry = viewModel::loadMore
isLoading = state.isLoading,
errorMsg = state.errorMsg,
isEnd = state.isEnd,
onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) }
)
}
}
@@ -367,7 +356,14 @@ fun ExploreShowScreen(
ExploreBookItem(
book = item.book,
shelfState = item.shelfState,
onClick = { onBookClick(item.book, sharedCoverKey) },
onClick = {
viewModel.onIntent(
ExploreShowIntent.OpenBook(
item.book,
sharedCoverKey
)
)
},
onLongClick = { book, coverKey ->
previewBook = book
previewSharedCoverKey = coverKey
@@ -381,15 +377,14 @@ fun ExploreShowScreen(
item {
LoadMoreFooter(
isLoading = isLoading,
errorMsg = errorMsg,
isEnd = isBookEnd,
onRetry = viewModel::loadMore
isLoading = state.isLoading,
errorMsg = state.errorMsg,
isEnd = state.isEnd,
onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) }
)
}
}
}
}
}
}
@@ -408,7 +403,7 @@ fun ExploreShowScreen(
onBookClick(book, sharedCoverKey)
},
onAddToShelf = { book ->
viewModel.onAddToShelf(book)
viewModel.onIntent(ExploreShowIntent.AddToShelf(book))
},
)
}
@@ -454,10 +449,9 @@ fun ExploreBookGridItem(
shelfState = shelfState,
onClick = onClick,
onLongClick = onLongClick,
modifier = modifier,
modifier = modifier.padding(4.dp),
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
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.rule.ExploreKind
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.BookShelfKey
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.utils.exploreLayoutGrid
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.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
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(
private val repository: ExploreRepository,
private val resolveBookShelfStateUseCase: ResolveBookShelfStateUseCase,
@@ -61,72 +33,128 @@ class ExploreShowViewModel(
) : ViewModel() {
private val _rawBooks = MutableStateFlow<List<SearchBook>>(emptyList())
private val _bookshelf = MutableStateFlow<Set<BookShelfKey>>(emptySet())
private val _isLoading = MutableStateFlow(false)
private val _isRefreshing = MutableStateFlow(false)
private val _isEnd = MutableStateFlow(false)
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 sourceUrl: String? = null
private var exploreUrl: String? = null
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) {
appCtx.exploreLayoutGrid = value
_gridCount.value = value
}
val uiBooks = combine(
_rawBooks,
_bookshelf
) { books, bookshelf ->
books.map { item ->
ExploreBookItemUi(
book = item,
shelfState = resolveBookShelfStateUseCase.execute(
name = item.name,
author = item.author,
url = item.bookUrl,
shelf = bookshelf
)
)
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val shouldTriggerAutoLoad = combine(
_isLoading,
uiBooks,
_rawBooks,
_isEndStateFlow
) { loading, uiBooks, rawBooks, isEnd ->
!loading && uiBooks.isEmpty() && rawBooks.isNotEmpty() && !isEnd
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
val isLoading = _isLoading.asStateFlow()
val errorMsg = _errorMsg.asStateFlow()
private val _effects = MutableSharedFlow<ExploreShowEffect>(extraBufferCapacity = 16)
val effects = _effects.asSharedFlow()
init {
viewModelScope.launch {
repository.getBookshelfItems().collect { list ->
val keys = list.map { BookShelfKey(it.name, it.author, it.bookUrl) }.toSet()
_bookshelf.value = keys
observeBookshelf()
combineUiState()
}
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)
}
}
}
fun initData(incomingSourceUrl: String?, incomingExploreUrl: String?) {
// 允许 incomingExploreUrl 为空,此时加载书源默认发现页
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,
_bookshelf,
_isLoading,
_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(
book = item,
shelfState = resolveBookShelfStateUseCase.execute(
name = item.name,
author = item.author,
url = item.bookUrl,
shelf = bookshelf,
)
)
}
ExploreShowUiState(
sourceUrl = sourceUrl,
books = books.toImmutableList(),
kinds = kinds.toImmutableList(),
selectedKindTitle = selectedKindTitle,
layoutState = _uiState.value.layoutState,
gridCount = _uiState.value.gridCount,
isLoading = isLoading,
isRefreshing = isLoading && page == 1,
isEnd = isEnd,
errorMsg = errorMsg,
sheet = _uiState.value.sheet,
)
}.collect { newState ->
val oldState = _uiState.value
_uiState.value = newState
if (newState.books.isEmpty() && _rawBooks.value.isNotEmpty() && !newState.isEnd && !newState.isLoading) {
loadMore()
}
}
}
}
private fun initData(incomingSourceUrl: String?, incomingExploreUrl: String?) {
if (sourceUrl == incomingSourceUrl && exploreUrl == incomingExploreUrl && bookSource != null) {
return
}
@@ -135,16 +163,16 @@ class ExploreShowViewModel(
page = 1
bookSource = null
_rawBooks.value = emptyList()
_isEndStateFlow.value = false
_isEnd.value = false
_errorMsg.value = null
_selectedKindTitle.value = null
_kinds.value = emptyList()
viewModelScope.launch {
if (bookSource == null && incomingSourceUrl != null) {
bookSource = repository.getBookSource(incomingSourceUrl)
}
// 如果仍然没有发现 URL,且书源已加载,尝试使用书源的默认发现页
if (exploreUrl == null && bookSource != null) {
loadKinds(incomingSourceUrl!!)
}
@@ -153,33 +181,36 @@ class ExploreShowViewModel(
}
}
fun loadKinds(sourceUrl: String) {
private fun loadKinds(sourceUrl: String) {
viewModelScope.launch {
_kinds.value = repository.getSourceExploreKinds(sourceUrl)
}
}
fun refreshKinds() {
sourceUrl?.let { loadKinds(it) }
}
fun switchExploreUrl(kind: ExploreKind) {
private fun switchKind(kind: ExploreKind) {
_selectedKindTitle.value = kind.title
exploreUrl = kind.url
_isEndStateFlow.value = false
_isEnd.value = false
loadMore(isRefresh = true)
}
fun setLayout() {
val newState = if (_layoutState.value == 0) 1 else 0
_layoutState.value = newState
AppConfig.exploreLayoutState = newState
private fun toggleLayout() {
_uiState.update {
val newState = if (it.layoutState == 0) 1 else 0
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 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 {
_isLoading.value = true
@@ -187,7 +218,7 @@ class ExploreShowViewModel(
if (isRefresh) {
page = 1
_isEndStateFlow.value = false
_isEnd.value = false
_rawBooks.value = emptyList()
}
@@ -195,7 +226,7 @@ class ExploreShowViewModel(
exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page)
}.onSuccess { result ->
if (result.books.isEmpty()) {
_isEndStateFlow.value = true
_isEnd.value = true
} else {
saveSearchBooksUseCase.save(result.books)
@@ -207,34 +238,22 @@ class ExploreShowViewModel(
.distinctBy { it.bookUrl }
if (uniqueNewBooks.isEmpty()) {
_isEndStateFlow.value = true
_isEnd.value = true
} else {
_rawBooks.value = currentList + uniqueNewBooks
page++
_isEndStateFlow.value = false
_isEnd.value = false
}
}
}.onFailure {
_errorMsg.value = it.stackTraceStr
}
.onFailure {
_errorMsg.value = it.stackTraceStr
}
_isLoading.value = false
}
}
fun getCurrentBookShelfState(item: SearchBook): BookShelfState {
return resolveBookShelfStateUseCase.execute(
name = item.name,
author = item.author,
url = item.bookUrl,
shelf = _bookshelf.value
)
}
fun onAddToShelf(book: SearchBook) {
viewModelScope.launch {
addToBookshelfUseCase.execute(book)
}
private fun emitEffect(effect: ExploreShowEffect) {
_effects.tryEmit(effect)
}
}
@@ -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.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.button.series.MediumPlainButton
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.modalBottomSheet.AppModalBottomSheet
@@ -236,11 +236,11 @@ fun GroupDeleteAction(
) {
var showDeleteDialog by remember { mutableStateOf(false) }
MediumIconButton(
MediumPlainButton(
onClick = {
showDeleteDialog = true
},
imageVector = Icons.Default.Delete
icon = Icons.Default.Delete
)
AppAlertDialog(
@@ -266,7 +266,7 @@ fun GroupResetCoverAction(
onCoverPathChange: (String?) -> Unit,
viewModel: GroupViewModel = koinViewModel()
) {
MediumIconButton(
MediumPlainButton(
onClick = {
if (group != null) {
viewModel.clearCover(group) {
@@ -277,6 +277,6 @@ fun GroupResetCoverAction(
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.SelectionActions
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.SmallTonalIconButton
import io.legado.app.ui.widget.components.button.series.SmallPlainButton
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.TextCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
@@ -427,9 +427,9 @@ private fun ImportPathNavigationBar(
}
if (canGoBack) {
SmallTonalIconButton(
SmallTonalButton(
onClick = onNavigateBack,
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "back"
)
}
@@ -511,9 +511,9 @@ private fun ImportBookItem(
if (!item.isDir && !item.isOnBookShelf) {
Spacer(modifier = Modifier.width(8.dp))
SmallIconButton(
SmallPlainButton(
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.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.button.series.SmallPlainButton
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.SelectionItemCard
import io.legado.app.ui.widget.components.card.TextCard
@@ -167,9 +167,9 @@ fun RemoteBookScreen(
},
endAction = if (showSheet is RemoteBookSheet.Servers) {
{
MediumIconButton(
MediumPlainButton(
onClick = { showSheet = RemoteBookSheet.ServerConfig(null) },
imageVector = Icons.Default.Add
icon = Icons.Default.Add
)
}
} else {
@@ -430,16 +430,16 @@ private fun ServerItem(
{
Row {
onEdit?.let {
SmallIconButton(
SmallPlainButton(
onClick = it,
imageVector = Icons.Default.Edit,
icon = Icons.Default.Edit,
contentDescription = "Edit"
)
}
onDelete?.let {
SmallIconButton(
SmallPlainButton(
onClick = it,
imageVector = Icons.Default.Delete,
icon = Icons.Default.Delete,
contentDescription = "Delete"
)
}
@@ -602,9 +602,9 @@ private fun PathNavigationBar(
}
if (canGoBack) {
SmallTonalIconButton(
SmallTonalButton(
onClick = onNavigateBack,
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "返回上级"
)
}
@@ -703,7 +703,7 @@ private fun RemoteBookItem(
if (!book.isDir) {
Spacer(modifier = Modifier.width(8.dp))
SmallIconButton(
SmallPlainButton(
onClick = {
if (book.isOnBookShelf) {
onUpdateClick(book)
@@ -711,7 +711,7 @@ private fun RemoteBookItem(
onAddClick(book)
}
},
imageVector = if (book.isOnBookShelf)
icon = if (book.isOnBookShelf)
Icons.Outlined.CloudSync
else
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.AppTextField
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.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon
@@ -1122,9 +1122,9 @@ private fun RelatedBooksBanner(
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f),
)
SmallTonalIconButton(
SmallTonalButton(
onClick = onMoreClick,
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
icon = Icons.AutoMirrored.Filled.ArrowForward,
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.EmptyMessage
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.SmallIconButton
import io.legado.app.ui.widget.components.button.series.MediumPlainButton
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.SelectionItemCard
import io.legado.app.ui.widget.components.checkBox.AppCheckbox
@@ -144,15 +144,15 @@ fun GroupSelectSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.group_select),
startAction = {
MediumIconButton(
MediumPlainButton(
onClick = { editingGroup = BookGroup() },
imageVector = Icons.Default.Add
icon = Icons.Default.Add
)
},
endAction = {
MediumIconButton(
MediumPlainButton(
onClick = { onConfirm(selectedGroupId) },
imageVector = Icons.Default.Check
icon = Icons.Default.Check
)
}
) {
@@ -186,9 +186,9 @@ fun GroupSelectSheet(
)
},
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { editingGroup = group },
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
},
containerColor = LegadoTheme.colorScheme.surfaceContainerLow
@@ -228,9 +228,9 @@ fun ChangeCoverSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.change_cover_source),
endAction = {
MediumIconButton(
MediumPlainButton(
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 = {
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
Box {
MediumIconButton(
MediumPlainButton(
onClick = { showOptionsMenu = true },
imageVector = Icons.Default.MoreVert
icon = Icons.Default.MoreVert
)
RoundDropdownMenu(
expanded = showOptionsMenu,
@@ -411,21 +411,21 @@ fun ChangeSourceSheet(
)
}
}
MediumIconButton(
MediumPlainButton(
onClick = { showMigrationOptions = true },
imageVector = Icons.Outlined.Settings
icon = Icons.Outlined.Settings
)
}
},
endAction = {
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
MediumIconButton(
MediumPlainButton(
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 },
imageVector = Icons.Default.FilterList
icon = Icons.Default.FilterList
)
}
}
@@ -476,11 +476,11 @@ fun ChangeSourceSheet(
containerColor = LegadoTheme.colorScheme.onSheetContent,
selectedContainerColor = LegadoTheme.colorScheme.primaryContainer.copy(alpha = 0.32f),
leadingContent = {
MediumIconButton(
MediumPlainButton(
onClick = {
viewModel.onBookScoreClick(item)
},
imageVector = Icons.Default.PushPin,
icon = Icons.Default.PushPin,
tint = if (bookScore > 0) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.outline,
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.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
import io.legado.app.ui.widget.components.button.series.MediumOutlinedButton
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -156,7 +156,7 @@ fun BookInfoEditContent(
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = {
(context as? BookInfoEditActivity)?.showDialogFragment(
ChangeCoverDialog(
@@ -165,15 +165,15 @@ fun BookInfoEditContent(
)
)
},
imageVector = Icons.Default.ImageSearch
icon = Icons.Default.ImageSearch
)
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = { selectCover.launch() },
imageVector = Icons.Default.FolderOpen
icon = Icons.Default.FolderOpen
)
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = { viewModel.resetCover() },
imageVector = Icons.Default.Replay
icon = Icons.Default.Replay
)
}
Spacer(modifier = Modifier.height(4.dp))
@@ -371,19 +371,19 @@ fun KindEditor(
}
}
Spacer(modifier = Modifier.width(8.dp))
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = {
onReset()
},
imageVector = Icons.Default.Replay
icon = Icons.Default.Replay
)
Spacer(modifier = Modifier.width(8.dp))
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = {
editingIndex = -1
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.FabMenuItem
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.SmallTonalTextButton
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.ReorderableSelectionItem
import io.legado.app.ui.widget.components.card.SelectionItemCard
@@ -768,32 +767,32 @@ private fun BookshelfManageScreen(
}
)
}
SmallTonalIconButton(
SmallTonalButton(
onClick = {
if (!book.isLocal) {
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"
)
SmallTonalIconButton(
SmallTonalButton(
onClick = { exportBook(book) },
imageVector = Icons.Default.Upload,
icon = Icons.Default.Upload,
contentDescription = "upload"
)
SmallTonalIconButton(
SmallTonalButton(
onClick = {
pendingMoveGroupBookUrl = book.bookUrl
groupPickerCurrentGroupId = book.group.coerceAtLeast(0L)
showGroupSelectSheet = true
},
imageVector = Icons.Default.Bookmarks,
icon = Icons.Default.Bookmarks,
contentDescription = "group"
)
SmallTonalIconButton(
SmallTonalButton(
onClick = { moreMenuBookUrl = book.bookUrl },
imageVector = Icons.Default.MoreVert,
icon = Icons.Default.MoreVert,
contentDescription = "more"
)
}
@@ -1288,12 +1287,12 @@ private fun BookSourcePickerSheet(
onDismissRequest = onDismissRequest,
title = title,
endAction = {
SmallTonalTextButton(
text = stringResource(android.R.string.ok),
imageVector = Icons.Default.PlayArrow,
SmallTonalButton(
onClick = {
onConfirm(selectedSources.mapNotNull { it.getBookSource() })
}
},
icon = Icons.Default.PlayArrow,
text = stringResource(android.R.string.ok)
)
}
) {
@@ -1402,17 +1401,17 @@ private fun BatchChangePreviewSheet(
onDismissRequest = onDismissRequest,
title = "批量换源预览",
startAction = {
SmallTonalTextButton(
text = "新增全部",
imageVector = Icons.Default.Add,
SmallTonalButton(
onClick = onAddAllToShelf,
icon = Icons.Default.Add,
text = "新增全部"
)
},
endAction = {
SmallTonalTextButton(
text = "迁移全部",
imageVector = Icons.Default.PlayArrow,
SmallTonalButton(
onClick = onMigrateAll,
icon = Icons.Default.PlayArrow,
text = "迁移全部"
)
}
) { items ->
@@ -1498,30 +1497,30 @@ private fun BatchChangePreviewRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
SmallTonalTextButton(
imageVector = Icons.Default.Search,
SmallTonalButton(
onClick = { onManualSearch(item.oldBook) },
icon = Icons.Default.Search
)
SmallTonalTextButton(
text = "不迁移",
imageVector = Icons.Default.Clear,
SmallTonalButton(
onClick = { onSkip(item.oldBook.bookUrl) },
icon = Icons.Default.Clear,
text = "不迁移"
)
SmallTonalTextButton(
text = "迁移",
imageVector = Icons.Default.PlayArrow,
SmallTonalButton(
onClick = { onMigrate(item.oldBook.bookUrl) },
icon = Icons.Default.PlayArrow,
text = "迁移"
)
SmallTonalTextButton(
text = "新增",
imageVector = Icons.Default.Add,
SmallTonalButton(
onClick = { onAddToShelf(item.oldBook.bookUrl) },
icon = Icons.Default.Add,
text = "新增"
)
if (item.candidates.size > 1) {
SmallTonalTextButton(
text = "查看其他源信息",
imageVector = Icons.Default.Info,
SmallTonalButton(
onClick = { onShowOtherSources(item) },
icon = Icons.Default.Info,
text = "查看其他源信息"
)
}
}
@@ -1605,9 +1604,9 @@ private fun OtherSourceOptionsSheet(
isSelected = index == currentItem.selectedCandidateIndex,
onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) },
trailingAction = {
SmallTonalIconButton(
SmallTonalButton(
onClick = { onOpenBook(candidate.book) },
imageVector = Icons.Default.Info,
icon = Icons.Default.Info,
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.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.button.series.MediumPlainButton
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.image.cover.CoilBookCover
@@ -187,9 +187,9 @@ fun DateNavigator(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
MediumIconButton(
MediumPlainButton(
onClick = onPrevClick,
imageVector = Icons.AutoMirrored.Filled.ArrowLeft
icon = Icons.AutoMirrored.Filled.ArrowLeft
)
AnimatedContent(
targetState = referenceDate,
@@ -224,9 +224,9 @@ fun DateNavigator(
modifier = Modifier.padding(horizontal = 16.dp)
)
}
MediumIconButton(
MediumPlainButton(
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.ui.theme.LegadoTheme
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.icon.AppIcons
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.book.SearchBookListItem
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.SelectionItemCard
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.EmptyMessage
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.SmallPlainButton
import io.legado.app.ui.widget.components.button.SmallToggleButton
import io.legado.app.ui.widget.components.button.ToggleStyle
import io.legado.app.ui.widget.components.button.series.MediumOutlinedButton
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.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon
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.bookmark.BookmarkEditSheet
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.TextCard
import io.legado.app.ui.widget.components.divider.PillDivider
@@ -476,10 +477,11 @@ fun TocScreen(
if (pagerState.currentPage == 0 && hasVolumes) {
Box {
SmallOutlinedIconToggleButton(
SmallToggleButton(
checked = showVolumeMenu,
onCheckedChange = { showVolumeMenu = it },
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
style = ToggleStyle.Outlined,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.volume_management)
)
RoundDropdownMenu(
@@ -5,24 +5,15 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
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.DraggableSelectionHandler
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.filePicker.FilePickerSheet
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.RuleEditSheet
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 sh.calvin.reorderable.rememberReorderableLazyListState
@@ -350,9 +340,9 @@ fun TxtRuleScreen(
onEnabledChange = { enabled -> viewModel.update(item.rule.copy(enable = enabled)) },
onClickEdit = { editingRule = item.rule; showEditSheet = true },
trailingAction = {
SmallIconButton(
SmallPlainButton(
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.model.BookCover
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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.toastOnUi
@@ -59,8 +59,8 @@ fun CoverRuleConfigSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.cover_rule),
startAction = {
MediumIconButton(
imageVector = Icons.Default.SettingsBackupRestore,
MediumPlainButton(
icon = Icons.Default.SettingsBackupRestore,
onClick = {
DefaultData.coverRule.let {
enable = it.enable
@@ -72,8 +72,8 @@ fun CoverRuleConfigSheet(
)
},
endAction = {
MediumIconButton(
imageVector = Icons.Default.Save,
MediumPlainButton(
icon = Icons.Default.Save,
onClick = {
if (searchUrl.isBlank() || coverRule.isBlank()) {
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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -16,12 +15,8 @@ import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
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.LaunchedEffect
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.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.button.series.MediumPlainButton
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.RoundDropdownMenuItem
@@ -70,18 +65,18 @@ fun DirectLinkUploadBottomSheet(
show = show,
title = stringResource(R.string.direct_link_upload_config),
startAction = {
MediumIconButton(
MediumPlainButton(
onClick = {
viewModel.testRule { result -> showTestResult = result }
},
imageVector = Icons.Default.Checklist
icon = Icons.Default.Checklist
)
},
endAction = {
Box {
MediumIconButton(
MediumPlainButton(
onClick = { showMenu = true },
imageVector = Icons.Default.MoreVert
icon = Icons.Default.MoreVert
)
RoundDropdownMenu(
expanded = showMenu,
@@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import io.legado.app.R
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.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcon
@@ -114,13 +114,13 @@ fun BackgroundImageManageSheet(
contentScale = ContentScale.Crop
)
}
SmallTonalIconButton(
SmallTonalButton(
onClick = { viewModel.removeBackground(isDark) },
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.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.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -30,8 +28,8 @@ import androidx.core.graphics.ColorUtils
import io.legado.app.R
import io.legado.app.help.config.TagColorGenerator
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.series.MediumOutlinedButton
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.TextCard
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
@@ -57,7 +55,7 @@ fun LabelColorManageSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.theme_config_manage_label_colors),
startAction = {
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = {
val baseColor = if (themeColor != 0) Color(themeColor) else primaryColor
val generatedColors = TagColorGenerator.generateTagColors(baseColor)
@@ -65,23 +63,25 @@ fun LabelColorManageSheet(
tagColors.addAll(generatedColors)
ThemeConfig.saveCustomTagColors(tagColors)
},
imageVector = Icons.Default.AutoAwesome
icon = Icons.Default.AutoAwesome
)
},
endAction = {
MediumOutlinedIconButton(
MediumOutlinedButton(
onClick = {
tagColors.add(TagColorPair(0, 0))
editingIndex = tagColors.size - 1
editingTextColor = 0
showColorPicker = true
},
imageVector = Icons.Default.Add
icon = Icons.Default.Add
)
}
) {
LazyColumn(
modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(tagColors.size) { index ->
@@ -110,20 +110,20 @@ fun LabelColorManageSheet(
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
SmallIconButton(
SmallPlainButton(
onClick = {
editingIndex = index
editingTextColor = colorPair.textColor
showColorPicker = true
},
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
SmallIconButton(
SmallPlainButton(
onClick = {
tagColors.removeAt(index)
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 io.legado.app.R
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.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -123,13 +123,13 @@ fun NavIconManageSheet(
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Fit
)
SmallTonalIconButton(
SmallTonalButton(
onClick = { dest.onSetPath("") },
modifier = Modifier
.align(Alignment.TopEnd)
.padding(4.dp)
.size(24.dp),
imageVector = Icons.Default.Close
icon = Icons.Default.Close
)
}
} 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.SplicedColumnGroup
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.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons
@@ -222,8 +222,8 @@ fun ThemeConfigScreen(
style = LegadoTheme.typography.labelLargeEmphasized,
modifier = Modifier.weight(1f)
)
SmallIconButton(
imageVector = AppIcons.Close,
SmallPlainButton(
icon = AppIcons.Close,
contentDescription = "关闭",
onClick = {
viewModel.setShowThemeRefactorTip(false)
@@ -759,8 +759,8 @@ fun ThemeConfigScreen(
onDismissRequest = { showFontSheet = false },
title = stringResource(R.string.font_setting),
startAction = {
SmallIconButton(
imageVector = Icons.Default.Delete,
SmallPlainButton(
icon = Icons.Default.Delete,
contentDescription = stringResource(R.string.clear),
onClick = {
ThemeConfig.appFontPath = null
@@ -769,8 +769,8 @@ fun ThemeConfigScreen(
)
},
endAction = {
SmallIconButton(
imageVector = Icons.Default.Add,
SmallPlainButton(
icon = Icons.Default.Add,
contentDescription = stringResource(R.string.select_folder),
onClick = { fontFolderLauncher.launch(null) }
)
@@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.help.config.ThemeExportData
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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.CompactClickableSettingItem
@@ -60,13 +60,13 @@ fun EditThemeSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.theme_manage_edit_theme),
endAction = {
MediumIconButton(
MediumPlainButton(
onClick = {
if (name.isNotBlank()) {
onSave(name, data)
}
},
imageVector = Icons.Default.Done,
icon = Icons.Default.Done,
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.SplicedColumnGroup
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.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.text.AppText
@@ -393,17 +393,17 @@ private fun SavedThemeItem(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
SmallIconButton(
SmallPlainButton(
onClick = onEdit,
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
SmallIconButton(
SmallPlainButton(
onClick = onExport,
imageVector = Icons.Default.Share
icon = Icons.Default.Share
)
SmallIconButton(
SmallPlainButton(
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.DraggableSelectionHandler
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.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons
@@ -315,9 +315,9 @@ fun DictRuleScreen(
onEnabledChange = { enabled -> viewModel.update(item.rule.copy(enabled = enabled)) },
onClickEdit = { editingRule = item.rule; showEditSheet = true },
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { showDeleteRuleDialog = item.rule },
imageVector = AppIcons.Delete
icon = AppIcons.Delete
)
}
)
@@ -111,7 +111,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
context: Context,
exploreName: String? = null,
sourceUrl: String,
exploreUrl: String? = null
exploreUrl: String? = null,
): Intent = MainIntent.createExploreShowIntent(context, exploreName, sourceUrl, exploreUrl)
}
@@ -134,7 +134,7 @@ object MainIntent {
context: Context,
exploreName: String? = null,
sourceUrl: String,
exploreUrl: String? = null
exploreUrl: String? = null,
): Intent {
return createLauncherIntent(context).apply {
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.AboutViewModel
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.ExploreShowViewModel
import io.legado.app.ui.book.import.local.ImportBookScreen
import io.legado.app.ui.book.import.remote.RemoteBookScreen
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.readConfig.ReadConfigScreen
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.translation.TranslationConfigScreen
import io.legado.app.ui.rss.article.MainRouteRssSort
import io.legado.app.ui.rss.article.RssSortRouteScreen
import io.legado.app.ui.rss.favorites.RssFavoritesScreen
@@ -436,10 +438,17 @@ fun MainActivity.mainEntryProvider(
}
entry<MainRouteExploreShow> { route ->
val exploreViewModel = koinViewModel<ExploreShowViewModel>()
LaunchedEffect(route.sourceUrl, route.exploreUrl, exploreViewModel) {
exploreViewModel.onIntent(
ExploreShowIntent.InitData(route.sourceUrl, route.exploreUrl)
)
}
ExploreShowScreen(
viewModel = exploreViewModel,
title = route.title ?: "探索",
sourceUrl = route.sourceUrl,
exploreUrl = route.exploreUrl,
onBack = { onNavigateBack() },
onBookClick = { book, sharedCoverKey ->
onNavigateToRoute(
@@ -268,7 +268,7 @@ object MainNavigator {
MainRouteExploreShow(
title = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_NAME),
sourceUrl = sourceUrl,
exploreUrl = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_URL)
exploreUrl = intent.getStringExtra(MainIntent.EXTRA_EXPLORE_URL),
)
} ?: 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.SearchBar
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.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
@@ -557,7 +559,7 @@ fun BookshelfScreen(
val showExpandButton by BookshelfConfig.shouldShowExpandButtonState
if (showExpandButton) {
Box(modifier = Modifier) {
SmallOutlinedIconToggleButton(
SmallToggleButton(
checked = showGroupMenu,
onCheckedChange = {
if (it) {
@@ -566,7 +568,8 @@ fun BookshelfScreen(
viewModel.dismissOverlay()
}
},
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
style = ToggleStyle.Outlined,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.group_manage)
)
RoundDropdownMenu(
@@ -837,7 +840,7 @@ fun BookshelfScreen(
if (this != null) Modifier.skipToLookaheadSize() else Modifier
}
),
beyondViewportPageCount = 1,
beyondViewportPageCount = 0,
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
) { pageIndex ->
val group = uiState.groups.getOrNull(pageIndex)
@@ -1058,9 +1061,10 @@ private fun BookshelfTopBar(
},
trailingIcon = {
if (uiState.searchKey.isNotEmpty()) {
TopBarActionButton(
SmallPlainButton(
modifier = Modifier.padding(start = 12.dp),
onClick = onClearSearch,
imageVector = AppIcons.Close,
icon = AppIcons.Close,
contentDescription = stringResource(R.string.clear)
)
}
@@ -41,7 +41,6 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
@@ -196,34 +195,31 @@ class BookshelfViewModel(
}.distinctUntilChanged().flowOn(Dispatchers.Default)
@OptIn(ExperimentalCoroutinesApi::class)
private val allGroupBooksFlow: StateFlow<Map<Long, List<BookUiItem>>> = combine(
groupsFlow, sortConfigFlow
) { groups, sortConfig ->
groups to sortConfig
}.flatMapLatest { (groups, sortConfig) ->
if (groups.isEmpty()) {
flowOf(emptyMap())
} else {
val flows = groups.map { group ->
bookRepository.flowBookShelfByGroup(group.groupId).map { books ->
group.groupId to bookshelfRepository.sortBooks(
books,
group,
sortConfig.sort,
sortConfig.sortOrder
).map { it.toUiItem() }
private val allGroupBooksImmutableFlow: StateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>> =
combine(groupsFlow, sortConfigFlow) { groups, sortConfig ->
groups to sortConfig
}.flatMapLatest { (groups, sortConfig) ->
if (groups.isEmpty()) {
flowOf(persistentMapOf())
} else {
val flows = groups.map { group ->
bookRepository.flowBookShelfByGroup(group.groupId).map { books ->
group.groupId to bookshelfRepository.sortBooks(
books,
group,
sortConfig.sort,
sortConfig.sortOrder
).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()
.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)
}.distinctUntilChanged()
.flowOn(Dispatchers.Default)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), persistentMapOf())
private val visibleBooksFlow: Flow<List<BookUiItem>> = combine(
@@ -301,30 +297,21 @@ class BookshelfViewModel(
}
}.distinctUntilChanged().flowOn(Dispatchers.Default)
private val coreInternalStateFlow = combine(
private val internalStateFlow = combine(
groupIdFlow,
searchKeyFlow,
searchModeFlow,
loadingTextFlow,
updatingBooksFlow
) { groupId, searchKey, isSearchMode, loadingText, updatingBooks ->
combine(updatingBooksFlow, upBooksCountFlow, sortConfigFlow) { a, b, c ->
Triple(a, b, c)
}
) { groupId, searchKey, isSearchMode, loadingText, (updatingBooks, upBooksCount, sortConfig) ->
InternalState(
groupId = groupId,
searchKey = searchKey,
isSearchMode = isSearchMode,
loadingText = loadingText,
updatingBooks = updatingBooks,
upBooksCount = 0,
sortConfig = readSortConfig()
)
}
private val internalStateFlow = combine(
coreInternalStateFlow,
upBooksCountFlow,
sortConfigFlow
) { core, upBooksCount, sortConfig ->
core.copy(
upBooksCount = upBooksCount,
sortConfig = sortConfig
)
@@ -351,35 +338,29 @@ class BookshelfViewModel(
val pendingSavedBooks: List<BookUiItem>?
)
private val editStateFlow = combine(
private val interactionStateFlow = combine(
activeOverlayFlow,
isEditModeFlow,
selectedVisibleBookUrlsFlow,
isInFolderRootFlow
) { activeOverlay, isEditMode, selectedBookUrls, isInFolderRoot ->
EditState(activeOverlay, isEditMode, selectedBookUrls, isInFolderRoot)
}
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 ->
isInFolderRootFlow,
isRefreshingFlow
) { activeOverlay, isEditMode, selectedBookUrls, isInFolderRoot, isRefreshing ->
BookshelfInteractionState(
activeOverlay = editState.activeOverlay,
isEditMode = editState.isEditMode,
selectedBookUrls = editState.selectedBookUrls,
isInFolderRoot = editState.isInFolderRoot,
activeOverlay = activeOverlay,
isEditMode = isEditMode,
selectedBookUrls = selectedBookUrls,
isInFolderRoot = isInFolderRoot,
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,
draggingBooks = draggingBooks,
pendingSavedBooks = pendingSavedBooks
@@ -390,11 +371,6 @@ class BookshelfViewModel(
GroupPreviewState(persistentMapOf(), persistentMapOf(), 0)
)
private val allGroupBooksStateFlow =
MutableStateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>>(
persistentMapOf()
)
private val dataStateFlow = combine(
booksFlow,
groupsFlow,
@@ -403,7 +379,7 @@ class BookshelfViewModel(
internalStateFlow
) { books, groups, allGroups, previews, internal ->
BookshelfDataCore(books, groups, allGroups, previews, internal)
}.combine(allGroupBooksStateFlow) { core, allGroupBooks ->
}.combine(allGroupBooksImmutableFlow) { core, allGroupBooks ->
BookshelfDataState(
books = core.books,
groups = core.groups.map { it.toBookGroupUi() },
@@ -529,9 +505,6 @@ class BookshelfViewModel(
viewModelScope.launch {
groupPreviewsFlow.collect { groupPreviewsStateFlow.value = it }
}
viewModelScope.launch {
allGroupBooksImmutableFlow.collect { allGroupBooksStateFlow.value = it }
}
viewModelScope.launch {
combine(booksFlow, selectedGroupCanReorderFlow) { books, canReorderBooks ->
books to canReorderBooks
@@ -26,12 +26,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
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.GroupEditContent
import io.legado.app.ui.book.group.GroupResetCoverAction
import io.legado.app.ui.book.group.GroupViewModel
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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.move
@@ -95,13 +95,13 @@ fun GroupManageSheet(
},
endAction = {
if (!isEditing) {
SmallIconButton(
SmallPlainButton(
onClick = {
editingGroup = null
coverPath = null
isEditing = true
},
imageVector = Icons.Default.Add
icon = Icons.Default.Add
)
} else {
GroupResetCoverAction(
@@ -92,6 +92,9 @@ fun ExploreScreen(
val context = LocalContext.current
val activity = context as? AppCompatActivity
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) }
val sourceToDelete = remember(sourceToDeleteUrl, uiState.items) {
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 {
val firstIndex = listState.firstVisibleItemIndex
val item = uiState.listItems.getOrNull(firstIndex)
val item = listItems.getOrNull(firstIndex)
if (item is ExploreListItem.KindRow) {
uiState.items.find { it.bookSourceUrl == item.sourceUrl }
} else {
@@ -178,7 +181,7 @@ fun ExploreScreen(
)
) {
items(
items = uiState.listItems,
items = listItems,
key = { it.key }
) { listItem ->
when (listItem) {
@@ -268,7 +271,7 @@ fun ExploreScreen(
verticalPadding = 8.dp,
onClick = {
scope.launch {
val index = uiState.listItems.indexOfFirst {
val index = listItems.indexOfFirst {
it is ExploreListItem.Header && it.source.bookSourceUrl == item.bookSourceUrl
}
if (index >= 0) listState.animateScrollToItem(index)
@@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -42,7 +41,6 @@ class ExploreViewModel(
private val _uiState = MutableStateFlow(ExploreUiState())
val uiState: StateFlow<ExploreUiState> = _uiState
.map { state -> state.copy(listItems = buildExploreListItems(state)) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ExploreUiState())
private val _effects = MutableSharedFlow<ExploreEffect>(extraBufferCapacity = 8)
val effects = _effects.asSharedFlow()
@@ -200,11 +198,10 @@ class ExploreViewModel(
val exploreKinds: ImmutableList<ExploreKind> = persistentListOf(),
val kindDisplayNames: ImmutableMap<String, String> = persistentMapOf(),
val kindValues: ImmutableMap<String, String> = persistentMapOf(),
val loadingKinds: Boolean = false,
val listItems: ImmutableList<ExploreListItem> = persistentListOf()
val loadingKinds: Boolean = false
) : ListUiState<BookSourcePart>
private fun buildExploreListItems(state: ExploreUiState): ImmutableList<ExploreListItem> {
fun buildExploreListItems(state: ExploreUiState): ImmutableList<ExploreListItem> {
if (state.items.isEmpty()) return persistentListOf()
val expandedId = state.expandedId
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.widget.components.AppTextField
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.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -127,38 +127,38 @@ fun <T> HomepageModuleManageSheet(
},
startAction = {
if (showCustomSetAddModules) {
SmallIconButton(
SmallPlainButton(
onClick = { showCustomSetAddModules = false },
imageVector = Icons.AutoMirrored.Filled.ArrowBack
icon = Icons.AutoMirrored.Filled.ArrowBack
)
} else if (browsingSourceUrl != null || showSourceBrowser) {
SmallIconButton(
SmallPlainButton(
onClick = {
if (browsingDetail) browsingDetail = false
else if (showSourceBrowser) showSourceBrowser = false
else browsingSourceUrl = null
},
imageVector = Icons.AutoMirrored.Filled.ArrowBack
icon = Icons.AutoMirrored.Filled.ArrowBack
)
} else if (selectingSetUrl != null) {
SmallIconButton(
SmallPlainButton(
onClick = { selectingSetUrl = null },
imageVector = Icons.AutoMirrored.Filled.ArrowBack
icon = Icons.AutoMirrored.Filled.ArrowBack
)
}
},
endAction = {
if (browsingDetail && browseTab == 2 && browseModuleType == "buttonGroup" && selectedKindTitles.isNotEmpty()) {
SmallIconButton(
SmallPlainButton(
onClick = { showAddButtonGroupDialog = true },
imageVector = Icons.Default.Check
icon = Icons.Default.Check
)
} else if ((showSourceBrowser || browsingSourceUrl != null) && !browsingDetail) {
var expanded by remember { mutableStateOf(false) }
Box {
SmallIconButton(
SmallPlainButton(
onClick = { expanded = true },
imageVector = Icons.Default.FilterList
icon = Icons.Default.FilterList
)
RoundDropdownMenu(
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.book.SearchBookGridItem
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.icon.AppIcon
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.TopBarActionButton
import io.legado.app.utils.sendToClip
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@@ -132,7 +133,7 @@ fun HomepageScreen(
}
LaunchedEffect(viewModel) {
viewModel.effects.collect { effect ->
viewModel.effects.collectLatest { effect ->
when (effect) {
is HomepageEffect.NavigateToBookInfo ->
onBookClick(
@@ -717,9 +718,9 @@ private fun ModuleHeader(
modifier = Modifier.weight(1f),
)
if (onNavigate != null) {
SmallTonalIconButton(
SmallTonalButton(
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) {
val json = source.homepageModules ?: return
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.theme.LegadoTheme
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.SelectionItemCard
import io.legado.app.ui.widget.components.divider.PillDivider
@@ -129,13 +129,13 @@ fun SetDetailPage(
}
},
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
SmallIconButton(
SmallPlainButton(
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,
onEnabledChange = { onToggleModule(module.id, it) },
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
SmallIconButton(
SmallPlainButton(
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.theme.LegadoTheme
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.divider.PillDivider
import io.legado.app.utils.move
@@ -85,13 +85,13 @@ fun SetListPage(
}
},
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { onRenameSet(set.sourceUrl) },
imageVector = Icons.Default.DriveFileRenameOutline
icon = Icons.Default.DriveFileRenameOutline
)
SmallIconButton(
SmallPlainButton(
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.theme.LegadoTheme
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.ReorderableSelectionItem
import io.legado.app.ui.widget.components.card.SelectionItemCard
@@ -170,13 +170,13 @@ fun SourceBrowseDetailPage(
}
},
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
SmallIconButton(
SmallPlainButton(
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,
onEnabledChange = { onToggleModule(module.id, it) },
trailingAction = {
SmallIconButton(
SmallPlainButton(
onClick = { onEditModule(module) },
imageVector = Icons.Default.Edit
icon = Icons.Default.Edit
)
SmallIconButton(
SmallPlainButton(
onClick = { onRequestDeleteModule(module.id) },
imageVector = Icons.Default.Delete
icon = Icons.Default.Delete
)
}
)
@@ -334,9 +334,9 @@ fun SourceBrowseDetailPage(
onToggleSelection = { showKindSelect = true },
trailingAction = {
if (isButtonGroup && selectedKindTitles.isNotEmpty()) {
SmallIconButton(
SmallPlainButton(
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.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.widget.components.AppScaffold
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.SwitchSettingItem
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.TopBarActionButton
import org.koin.androidx.compose.koinViewModel
@@ -81,7 +80,7 @@ fun MyScreen(
GlassMediumFlexibleTopAppBar(
title = stringResource(R.string.my),
actions = {
IconButton(
TopBarActionButton(
onClick = {
onNavigate(
PrefClickEvent.ShowMd(
@@ -89,10 +88,10 @@ fun MyScreen(
path = "appHelp"
)
)
}
) {Icon(
Icons.AutoMirrored.Filled.HelpOutline, null)
}
},
imageVector = Icons.AutoMirrored.Filled.HelpOutline,
contentDescription = null
)
},
scrollBehavior = scrollBehavior
)
@@ -251,22 +250,22 @@ fun WebServiceSettingBlock(
.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.End
) {
SmallTextButton(
text = stringResource(R.string.copy_url),
imageVector = Icons.Default.ContentCopy,
SmallPlainButton(
onClick = {
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
}
},
icon = Icons.Default.ContentCopy,
text = stringResource(R.string.copy_url)
)
Spacer(modifier = Modifier.width(12.dp))
SmallTextButton(
text = stringResource(R.string.open_in_browser),
imageVector = Icons.Default.OpenInBrowser,
SmallPlainButton(
onClick = {
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.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
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.height
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.Save
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
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.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.AppTextField
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.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
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.icon.AppIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
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
@Composable
@@ -369,9 +350,9 @@ fun GroupSelector(
}
}
}
MediumIconButton(
MediumPlainButton(
onClick = onManageClick,
imageVector = Icons.Default.Settings
icon = Icons.Default.Settings
)
}
}
@@ -1,7 +1,6 @@
package io.legado.app.ui.rss.article
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.size
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.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
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.Login
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.Edit
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.Style
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -39,6 +35,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
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.widget.components.AppScaffold
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.SmallOutlinedIconToggleButton
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.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.GlassCard
import io.legado.app.ui.widget.components.icon.AppIcons
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.topbar.GlassMediumFlexibleTopAppBar
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
@OptIn(ExperimentalMaterial3Api::class)
@@ -242,10 +240,11 @@ fun RssSortScreen(
if (BookshelfConfig.shouldShowExpandButton) {
Box {
SmallOutlinedIconToggleButton(
SmallToggleButton(
checked = showGroupMenu,
onCheckedChange = { showGroupMenu = it },
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
style = ToggleStyle.Outlined,
icon = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.group_manage)
)
RoundDropdownMenu(
@@ -321,9 +320,9 @@ private fun RssReadRecordSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.read_record),
endAction = {
SmallIconButton(
SmallPlainButton(
onClick = onClear,
imageVector = Icons.Default.CleaningServices,
icon = Icons.Default.CleaningServices,
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.widget.components.ActionItem
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.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider
@@ -229,9 +229,9 @@ fun RssFavoritesScreen(
val openAction = {
onOpenRead(rssStar.title, rssStar.origin, rssStar.link, null)
}
SmallIconButton(
SmallPlainButton(
onClick = openAction,
imageVector = Icons.AutoMirrored.Filled.OpenInNew,
icon = Icons.AutoMirrored.Filled.OpenInNew,
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.AppTextField
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.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -422,9 +422,9 @@ private fun FavoriteEditSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.favorite),
endAction = {
SmallIconButton(
SmallPlainButton(
onClick = onDelete,
imageVector = Icons.Default.CleaningServices,
icon = Icons.Default.CleaningServices,
contentDescription = stringResource(R.string.delete)
)
}
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.GroupManageBottomSheet
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.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider
@@ -364,9 +360,9 @@ fun RssSourceScreen(
onEnabledChange = { enabled -> viewModel.update(item.source.copy(enabled = enabled)) },
onClickEdit = { onEditSource(item.source) },
trailingAction = {
SmallIconButton(
SmallPlainButton(
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.animateFloatingActionButton
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.EmptyMessage
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.checkBox.CheckboxGroupContainer
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
@@ -165,9 +165,11 @@ fun RuleSubScreen(
}
},
trailingAction = {
IconButton(onClick = { showEditDialog = ruleSub }) {
Icon(Icons.Default.Edit, contentDescription = "Edit")
}
SmallPlainButton(
onClick = { showEditDialog = ruleSub },
icon = Icons.Default.Edit,
contentDescription = "Edit"
)
},
dropdownContent = { dismiss ->
RoundDropdownMenuItem(
@@ -35,6 +35,14 @@ fun Modifier.adaptiveVerticalPadding(): Modifier {
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
fun adaptiveContentPaddingOnlyVertical(
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.widthIn
import androidx.compose.foundation.layout.wrapContentSize
import io.legado.app.ui.widget.components.progressIndicator.AppContainedLoadingIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.sp
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.button.SmallTonalTextButton
import io.legado.app.ui.widget.components.progressIndicator.AppContainedLoadingIndicator
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@Composable
@@ -84,10 +84,10 @@ fun EmptyMessage(
if (buttonText != null && onButtonClick != null) {
Spacer(modifier = Modifier.height(8.dp))
SmallTonalTextButton(
SmallTonalButton(
onClick = onButtonClick,
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.unit.dp
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.settingItem.SettingItem
@@ -118,13 +118,13 @@ private fun GroupItem(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
SmallTextButton(
text = stringResource(id = R.string.ok),
imageVector = Icons.Default.Check,
SmallPlainButton(
onClick = {
onUpdateGroup(group, state.text.toString())
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.JsonParser
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.utils.GSON
@@ -37,17 +37,17 @@ fun JsonRawEditor(
style = LegadoTheme.typography.labelMediumEmphasized
)
Row {
SmallIconButton(
SmallPlainButton(
onClick = {
runCatching {
val jsonElement = JsonParser.parseString(value)
onValueChange(GSON.toJson(jsonElement))
}
},
imageVector = Icons.Default.AutoFixHigh,
icon = Icons.Default.AutoFixHigh,
contentDescription = "格式化"
)
SmallIconButton(
SmallPlainButton(
onClick = {
runCatching {
val jsonElement = JsonParser.parseString(value)
@@ -55,7 +55,7 @@ fun JsonRawEditor(
onValueChange(compactGson.toJson(jsonElement))
}
},
imageVector = Icons.Default.Compress,
icon = Icons.Default.Compress,
contentDescription = "压缩"
)
}
@@ -10,7 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallOutlinedIconButton
import io.legado.app.ui.widget.components.button.series.SmallOutlinedButton
import io.legado.app.ui.widget.components.card.TextCard
@Composable
@@ -26,12 +26,12 @@ fun ValueStepper(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
SmallOutlinedIconButton(
SmallOutlinedButton(
onClick = {
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
onValueChange(newValue)
},
imageVector = Icons.Default.Remove
icon = Icons.Default.Remove
)
TextCard(
cornerRadius = 8.dp,
@@ -41,12 +41,12 @@ fun ValueStepper(
backgroundColor = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurface
)
SmallOutlinedIconButton(
SmallOutlinedButton(
onClick = {
val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
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.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.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.padding
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.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.ThemeResolver
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.icon.AppIcons
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.ReorderableLazyListState
import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable
fun SelectionItemCard(
@@ -218,9 +212,9 @@ fun SelectionItemCardContent(
}
if (onClickEdit != null) {
SmallIconButton(
SmallPlainButton(
onClick = onClickEdit,
imageVector = AppIcons.Edit,
icon = AppIcons.Edit,
contentDescription = "Edit"
)
}
@@ -231,9 +225,9 @@ fun SelectionItemCardContent(
if (dropdownContent != null) {
Box {
SmallIconButton(
SmallPlainButton(
onClick = { showMenu = true },
imageVector = AppIcons.MoreVert,
icon = AppIcons.MoreVert,
contentDescription = "More"
)
RoundDropdownMenu(
@@ -36,8 +36,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.MediumAnimatedActionButton
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.series.MediumToggleButton
import io.legado.app.ui.widget.components.text.AppText
import java.time.LocalDate
@@ -54,15 +54,14 @@ fun HeatmapCalendarStartAction(
currentMode: HeatmapMode,
onModeChanged: (HeatmapMode) -> Unit,
) {
MediumAnimatedActionButton(
MediumToggleButton(
checked = currentMode == HeatmapMode.TIME,
onCheckedChange = {
onModeChanged(if (it) HeatmapMode.TIME else HeatmapMode.COUNT)
},
icon = Icons.Default.FormatListNumbered,
iconChecked = Icons.Default.AccessTime,
iconUnchecked = Icons.Default.FormatListNumbered,
activeText = "按时长",
inactiveText = "按次数"
text = "按时长"
)
}
@@ -73,9 +72,9 @@ fun HeatmapCalendarStartAction(
fun HeatmapCalendarEndAction(
onClearDate: () -> Unit
) {
MediumOutlinedIconButton(
MediumOutlinedButton(
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.alert.AppAlertDialog
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.SelectionItemCard
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -183,9 +183,9 @@ fun <T> BatchImportDialog(
title = sheetTitle,
startAction = if (isEditing) {
{
SmallIconButton(
SmallPlainButton(
onClick = { editingIndex = null },
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
icon = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "返回"
)
}
@@ -196,9 +196,9 @@ fun <T> BatchImportDialog(
{
Row {
topBarActions()
SmallIconButton(
SmallPlainButton(
onClick = { onToggleAll(!allSelected) },
imageVector = Icons.Default.SelectAll,
icon = Icons.Default.SelectAll,
contentDescription = if (allSelected) "全不选" else "全选"
)
}
@@ -371,9 +371,9 @@ fun ImportItemRow(
modifier = Modifier.padding(end = 4.dp)
)
SmallIconButton(
SmallPlainButton(
onClick = onInfoClick,
imageVector = Icons.Default.Info,
icon = Icons.Default.Info,
contentDescription = "详情"
)
}
@@ -23,7 +23,7 @@ import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.ui.theme.LegadoTheme
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.text.AppText
import io.legado.app.utils.LogUtils
@@ -43,12 +43,12 @@ fun AppLogSheet(
onDismissRequest = onDismissRequest,
title = stringResource(R.string.log),
endAction = {
MediumIconButton(
MediumPlainButton(
onClick = {
AppLog.clear()
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.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -65,7 +63,7 @@ fun RowScope.OptionCard(
.weight(1f)
.height(100.dp),
cornerRadius = 12.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
containerColor = LegadoTheme.colorScheme.onSheetContent,
elevation = 2.dp
) {
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.MoreVert
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.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.getValue
import androidx.compose.runtime.mutableStateOf
@@ -30,19 +23,16 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch
/**
@@ -84,17 +74,17 @@ fun <T> RuleEditSheet(
AppModalBottomSheet(
title = title,
startAction = {
MediumIconButton(
MediumPlainButton(
onClick = onDismissRequest,
imageVector = Icons.Default.Close,
icon = Icons.Default.Close,
contentDescription = "Close",
)
},
endAction = {
Box{
MediumIconButton(
MediumPlainButton(
onClick = { showMenu = true },
imageVector = Icons.Default.MoreVert,
icon = Icons.Default.MoreVert,
contentDescription = "More"
)
RoundDropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
@@ -1,9 +1,13 @@
package io.legado.app.ui.widget.components.topbar
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.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
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.lerp
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.theme.LegadoTheme
import io.legado.app.ui.theme.LocalHazeState
@@ -81,7 +86,9 @@ fun GlassMediumFlexibleTopAppBar(
}
val finalModifier = if (hazeState != null) {
modifier.background(color = animatedColor).responsiveHazeEffect(state = hazeState)
modifier
.background(color = animatedColor)
.responsiveHazeEffect(state = hazeState)
} else {
modifier.background(color = animatedColor)
}
@@ -116,7 +123,7 @@ fun GlassMediumFlexibleTopAppBar(
AdaptiveAnimatedText(
text = title,
useCharMode = useCharMode,
maxLines = 1,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
},
@@ -126,7 +133,13 @@ fun GlassMediumFlexibleTopAppBar(
}
},
navigationIcon = navigationIcon,
actions = actions,
actions = {
Box(modifier = Modifier.padding(end = 12.dp)) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { actions() }
}
},
scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
colors = transparentColors
)
@@ -153,7 +166,15 @@ fun GlassMediumFlexibleTopAppBar(
}
},
navigationIcon = navigationIcon,
actions = actions,
actions = {
Box(modifier = Modifier.padding(end = 12.dp)) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
actions()
}
}
},
scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
colors = transparentColors
)
@@ -30,13 +30,13 @@ import io.legado.app.R
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.button.AnimatedActionButtonCore
import io.legado.app.ui.widget.components.button.AnimatedIcon
import io.legado.app.ui.widget.components.button.series.AnimatedActionButtonCore
import io.legado.app.ui.widget.components.button.series.AnimatedIcon
import io.legado.app.ui.widget.components.icon.AppIcons
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.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
private fun TopBarButton(
@@ -112,7 +112,7 @@ fun TopBarActionButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier.padding(end = 12.dp)
modifier = modifier
)
} else {
IconButton(
@@ -140,16 +140,10 @@ fun TopBarAnimatedActionButton(
modifier: Modifier = Modifier
) {
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(
targetValue = if (checked) MiuixTheme.colorScheme.onPrimaryContainer else MiuixTheme.colorScheme.onSurface,
targetValue = if (checked) MiuixTheme.colorScheme.primary else MiuixTheme.colorScheme.onSurface,
animationSpec = tween(150),
label = "MiuixActionButtonContainer"
label = "MiuixActionButtonContent"
)
AnimatedActionButtonCore(
@@ -167,8 +161,7 @@ fun TopBarAnimatedActionButton(
button = { buttonModifier, onToggle, content ->
MiuixIconButton(
onClick = { onToggle(!checked) },
modifier = buttonModifier,
backgroundColor = containerColor
modifier = buttonModifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -1,24 +1,41 @@
package io.legado.app.web.socket
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import io.ktor.server.websocket.DefaultWebSocketServerSession
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.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.MatchMode
import io.legado.app.domain.usecase.BookSearchControl
import io.legado.app.domain.usecase.BookSearchRequest
import io.legado.app.domain.usecase.SearchBooksUseCase
import io.legado.app.domain.usecase.SearchRunEvent
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.utils.*
import kotlinx.coroutines.*
import io.legado.app.utils.GSON
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 splitties.init.appCtx
class BookSearchWebSocket(private val session: DefaultWebSocketServerSession) : CoroutineScope by session {
private val searchBooksUseCase: SearchBooksUseCase by lazy { GlobalContext.get().get() }
private val localPreferencesRepository: LocalPreferencesRepository by lazy {
GlobalContext.get().get()
}
private val searchControl = BookSearchControl()
private val sentBookUrls = linkedSetOf<String>()
private var searchJob: Job? = null
@@ -66,7 +83,14 @@ class BookSearchWebSocket(private val session: DefaultWebSocketServerSession) :
keyword = key,
page = 1,
scope = BookSearchScope(AppConfig.searchScope),
precision = appCtx.getPrefBoolean(PreferKey.precisionSearch),
matchMode = MatchMode.of(
localPreferencesRepository
.getPreference(
LocalPreferencesKeys.MATCH_MODE,
MatchMode.DEFAULT.value
)
.first()
),
concurrency = OtherConfig.threadCount,
),
searchControl
+4 -2
View File
@@ -24,7 +24,7 @@
<string name="dialog_setting">去设置</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="contrast_source_system_summary">来源:系统对比度</string>
<string name="contrast_source_manual_summary">来源:手动输入</string>
@@ -897,7 +897,7 @@
<string name="body_title">正文标题</string>
<string name="show_hide">显示/隐藏</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="get_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_empty_scope_disable_precision">%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="http_cache">HTTP缓存</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_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_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="http_cache">HTTP Cache</string>
<string name="cover_cache">Cover Cache</string>