[优化] 增加大量 Compose 界面的模糊
This commit is contained in:
16 files changed
+253
-150
No files matched your search
@@ -44,11 +44,12 @@ sealed interface BaseRuleEvent {
|
|||||||
|
|
||||||
abstract class BaseRuleViewModel<T : SelectableItem<ID>, Entity, ID, S : ListUiState<T>>(
|
abstract class BaseRuleViewModel<T : SelectableItem<ID>, Entity, ID, S : ListUiState<T>>(
|
||||||
application: Application,
|
application: Application,
|
||||||
initialState: S,
|
protected val initialState: S,
|
||||||
private val uploadRepository: UploadRepository? = null // 设为可空,提高灵活性
|
private val uploadRepository: UploadRepository? = null // 设为可空,提高灵活性
|
||||||
) : BaseViewModel(application) {
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
protected val _searchKey = MutableStateFlow("")
|
protected val _searchKey = MutableStateFlow("")
|
||||||
|
protected val _groupFilter = MutableStateFlow("")
|
||||||
protected val _selectedIds = MutableStateFlow<Set<ID>>(emptySet())
|
protected val _selectedIds = MutableStateFlow<Set<ID>>(emptySet())
|
||||||
protected val _isSearchMode = MutableStateFlow(false)
|
protected val _isSearchMode = MutableStateFlow(false)
|
||||||
protected val _isUploading = MutableStateFlow(false)
|
protected val _isUploading = MutableStateFlow(false)
|
||||||
@@ -60,24 +61,34 @@ abstract class BaseRuleViewModel<T : SelectableItem<ID>, Entity, ID, S : ListUiS
|
|||||||
|
|
||||||
abstract val rawDataFlow: Flow<List<Entity>>
|
abstract val rawDataFlow: Flow<List<Entity>>
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Use filterData with groupFilter instead",
|
||||||
|
ReplaceWith("filterData(data, searchKey, \"\")")
|
||||||
|
)
|
||||||
open fun filterData(data: List<Entity>, key: String): List<Entity> = data
|
open fun filterData(data: List<Entity>, key: String): List<Entity> = data
|
||||||
|
|
||||||
|
open fun filterData(data: List<Entity>, searchKey: String, groupFilter: String): List<Entity> {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
return filterData(data, if (groupFilter.isNotEmpty()) groupFilter else searchKey)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
private val itemsFlow: Flow<List<T>> by lazy {
|
private val itemsFlow: Flow<List<T>> by lazy {
|
||||||
combine(
|
combine(
|
||||||
rawDataFlow,
|
rawDataFlow,
|
||||||
_searchKey,
|
_searchKey,
|
||||||
|
_groupFilter,
|
||||||
_localItems
|
_localItems
|
||||||
) { data, key, local ->
|
) { data, searchKey, groupFilter, local ->
|
||||||
if (local != null && key.isEmpty()) {
|
if (local != null && searchKey.isEmpty() && groupFilter.isEmpty()) {
|
||||||
local
|
local
|
||||||
} else {
|
} else {
|
||||||
filterData(data, key).map { it.toUiItem() }
|
filterData(data, searchKey, groupFilter).map { it.toUiItem() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val uiState: StateFlow<S> by lazy {
|
open val uiState: StateFlow<S> by lazy {
|
||||||
combine(
|
combine(
|
||||||
itemsFlow,
|
itemsFlow,
|
||||||
_selectedIds,
|
_selectedIds,
|
||||||
@@ -132,6 +143,11 @@ abstract class BaseRuleViewModel<T : SelectableItem<ID>, Entity, ID, S : ListUiS
|
|||||||
_searchKey.value = key ?: ""
|
_searchKey.value = key ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun setGroupFilter(filter: String?) {
|
||||||
|
_localItems.value = null
|
||||||
|
_groupFilter.value = filter ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
fun setSearchMode(active: Boolean) {
|
fun setSearchMode(active: Boolean) {
|
||||||
_isSearchMode.value = active
|
_isSearchMode.value = active
|
||||||
if (!active) setSearchKey("")
|
if (!active) setSearchKey("")
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import io.legado.app.ui.book.searchContent.SearchContentViewModel
|
|||||||
import io.legado.app.ui.book.toc.TocViewModel
|
import io.legado.app.ui.book.toc.TocViewModel
|
||||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleViewModel
|
import io.legado.app.ui.book.toc.rule.TxtTocRuleViewModel
|
||||||
import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
|
import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
|
||||||
|
import io.legado.app.ui.config.readConfig.ReadConfigViewModel
|
||||||
import io.legado.app.ui.dict.rule.DictRuleViewModel
|
import io.legado.app.ui.dict.rule.DictRuleViewModel
|
||||||
import io.legado.app.ui.main.my.MyViewModel
|
import io.legado.app.ui.main.my.MyViewModel
|
||||||
import io.legado.app.ui.replace.ReplaceEditRoute
|
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||||
import io.legado.app.ui.replace.ReplaceRuleViewModel
|
import io.legado.app.ui.replace.ReplaceRuleViewModel
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
||||||
|
import io.legado.app.ui.rss.source.manage.RssSourceViewModel
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.core.module.dsl.viewModel
|
import org.koin.core.module.dsl.viewModel
|
||||||
import org.koin.core.module.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
@@ -43,6 +45,7 @@ val appModule = module {
|
|||||||
single<ExploreRepository> { ExploreRepositoryImpl(get()) }
|
single<ExploreRepository> { ExploreRepositoryImpl(get()) }
|
||||||
|
|
||||||
viewModelOf(::DictRuleViewModel)
|
viewModelOf(::DictRuleViewModel)
|
||||||
|
viewModelOf(::RssSourceViewModel)
|
||||||
viewModelOf(::ReadRecordViewModel)
|
viewModelOf(::ReadRecordViewModel)
|
||||||
viewModelOf(::ExploreShowViewModel)
|
viewModelOf(::ExploreShowViewModel)
|
||||||
viewModelOf(::MyViewModel)
|
viewModelOf(::MyViewModel)
|
||||||
@@ -50,6 +53,7 @@ val appModule = module {
|
|||||||
viewModelOf(::AllBookmarkViewModel)
|
viewModelOf(::AllBookmarkViewModel)
|
||||||
viewModelOf(::TxtTocRuleViewModel)
|
viewModelOf(::TxtTocRuleViewModel)
|
||||||
viewModelOf(::OtherConfigViewModel)
|
viewModelOf(::OtherConfigViewModel)
|
||||||
|
viewModelOf(::ReadConfigViewModel)
|
||||||
viewModelOf(::TocViewModel)
|
viewModelOf(::TocViewModel)
|
||||||
viewModelOf(::RemoteBookViewModel)
|
viewModelOf(::RemoteBookViewModel)
|
||||||
viewModelOf(::BookInfoViewModel)
|
viewModelOf(::BookInfoViewModel)
|
||||||
|
|||||||
@@ -106,18 +106,14 @@ object ReadBookConfig {
|
|||||||
shareConfig = c ?: configList.getOrNull(5) ?: Config()
|
shareConfig = c ?: configList.getOrNull(5) ?: Config()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upBg(width: Int, height: Int): Drawable? {
|
fun upBg(width: Int, height: Int) {
|
||||||
val drawable = durConfig.curBgDrawable(width, height)
|
val drawable = durConfig.curBgDrawable(width, height)
|
||||||
if (drawable is BitmapDrawable && drawable.bitmap != null) {
|
if (drawable is BitmapDrawable && drawable.bitmap != null) {
|
||||||
bgMeanColor = drawable.bitmap.getMeanColor()
|
bgMeanColor = drawable.bitmap.getMeanColor()
|
||||||
} else if (drawable is ColorDrawable) {
|
} else if (drawable is ColorDrawable) {
|
||||||
bgMeanColor = drawable.color
|
bgMeanColor = drawable.color
|
||||||
}
|
}
|
||||||
val tmp = bg
|
|
||||||
bg = drawable
|
bg = drawable
|
||||||
// 返回旧 Drawable,由调用方在视图更新完成后再 recycle,
|
|
||||||
// 避免视图仍引用旧 BitmapDrawable 时 bitmap 已被 recycle 导致崩溃
|
|
||||||
return tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
@@ -1154,4 +1150,4 @@ object ReadBookConfig {
|
|||||||
return bgDrawable ?: fallbackColor.toDrawable()
|
return bgDrawable ?: fallbackColor.toDrawable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -308,13 +307,17 @@ fun TxtRuleScreen(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(modifier = Modifier
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
state = listState,
|
state = listState,
|
||||||
contentPadding = PaddingValues(top = 8.dp, bottom = 120.dp),
|
contentPadding = PaddingValues(
|
||||||
|
top = paddingValues.calculateTopPadding() + 8.dp,
|
||||||
|
bottom = paddingValues.calculateBottomPadding() + 120.dp
|
||||||
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
items(rules, key = { it.id }) { item ->
|
items(rules, key = { it.id }) { item ->
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -304,13 +303,17 @@ fun DictRuleScreen(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(modifier = Modifier
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
state = listState,
|
state = listState,
|
||||||
contentPadding = PaddingValues(top = 8.dp, bottom = 120.dp),
|
contentPadding = PaddingValues(
|
||||||
|
top = paddingValues.calculateTopPadding() + 8.dp,
|
||||||
|
bottom = paddingValues.calculateBottomPadding() + 120.dp
|
||||||
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
items(rules, key = { it.id }) { item ->
|
items(rules, key = { it.id }) { item ->
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import androidx.compose.material3.DropdownMenuItem
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LoadingIndicator
|
import androidx.compose.material3.LoadingIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -65,7 +64,10 @@ import io.legado.app.base.BaseRuleEvent
|
|||||||
import io.legado.app.data.entities.ReplaceRule
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
import io.legado.app.ui.widget.components.ActionItem
|
import io.legado.app.ui.widget.components.ActionItem
|
||||||
import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
||||||
|
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||||
|
import io.legado.app.ui.widget.components.GroupManageBottomSheet
|
||||||
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||||
|
import io.legado.app.ui.widget.components.divider.PillDivider
|
||||||
import io.legado.app.ui.widget.components.exportComponents.FilePickerSheet
|
import io.legado.app.ui.widget.components.exportComponents.FilePickerSheet
|
||||||
import io.legado.app.ui.widget.components.exportComponents.FilePickerSheetMode
|
import io.legado.app.ui.widget.components.exportComponents.FilePickerSheetMode
|
||||||
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||||
@@ -252,7 +254,8 @@ fun ReplaceRuleScreen(
|
|||||||
GroupManageBottomSheet(
|
GroupManageBottomSheet(
|
||||||
groups = groups,
|
groups = groups,
|
||||||
onDismissRequest = { showGroupManageSheet = false },
|
onDismissRequest = { showGroupManageSheet = false },
|
||||||
viewModel = viewModel
|
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
|
||||||
|
onDeleteGroup = { viewModel.delGroup(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,6 +335,7 @@ fun ReplaceRuleScreen(
|
|||||||
.coerceAtLeast(0),
|
.coerceAtLeast(0),
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
divider = {},
|
divider = {},
|
||||||
|
containerColor = GlassTopAppBarDefaults.containerColor(),
|
||||||
) {
|
) {
|
||||||
tabItems.forEachIndexed { index, title ->
|
tabItems.forEachIndexed { index, title ->
|
||||||
Tab(
|
Tab(
|
||||||
@@ -403,7 +407,7 @@ fun ReplaceRuleScreen(
|
|||||||
text = { Text("帮助") },
|
text = { Text("帮助") },
|
||||||
onClick = { /*TODO*/ dismiss() }
|
onClick = { /*TODO*/ dismiss() }
|
||||||
)
|
)
|
||||||
HorizontalDivider()
|
PillDivider()
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = { Text("旧的在前") },
|
text = { Text("旧的在前") },
|
||||||
onClick = { viewModel.setSortMode("asc"); dismiss() }
|
onClick = { viewModel.setSortMode("asc"); dismiss() }
|
||||||
@@ -436,7 +440,6 @@ fun ReplaceRuleScreen(
|
|||||||
) { padding ->
|
) { padding ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
@@ -444,8 +447,8 @@ fun ReplaceRuleScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
top = 8.dp,
|
top = padding.calculateTopPadding() + 8.dp,
|
||||||
bottom = 120.dp
|
bottom = padding.calculateBottomPadding() + 120.dp
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ fun Modifier.responsiveHazeEffect(
|
|||||||
|
|
||||||
if (!enableBlur) return this
|
if (!enableBlur) return this
|
||||||
|
|
||||||
|
val style = if (enableProgressiveBlur) {
|
||||||
|
HazeMaterials.ultraThin()
|
||||||
|
} else {
|
||||||
|
HazeMaterials.regular()
|
||||||
|
}
|
||||||
|
|
||||||
return this.hazeEffect(
|
return this.hazeEffect(
|
||||||
state = state,
|
state = state,
|
||||||
style = HazeMaterials.ultraThin()
|
style = style
|
||||||
) {
|
) {
|
||||||
if (enableProgressiveBlur) {
|
if (enableProgressiveBlur) {
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive = HazeProgressive.verticalGradient(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.widthIn
|
|||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.material3.ContainedLoadingIndicator
|
import androidx.compose.material3.ContainedLoadingIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -69,6 +70,7 @@ fun EmptyMessageView(
|
|||||||
|
|
||||||
AnimatedTextLine(
|
AnimatedTextLine(
|
||||||
text = message,
|
text = message,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
softWrap = true,
|
softWrap = true,
|
||||||
|
|||||||
+15
-2
@@ -43,7 +43,7 @@ object GlassTopAppBarDefaults {
|
|||||||
val enableBlur = ThemeConfig.enableBlur
|
val enableBlur = ThemeConfig.enableBlur
|
||||||
|
|
||||||
val containerColor = if (enableBlur) {
|
val containerColor = if (enableBlur) {
|
||||||
MaterialTheme.colorScheme.surface
|
MaterialTheme.colorScheme.surface.copy(alpha = 0f)
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.surface.copy(alpha = alpha)
|
MaterialTheme.colorScheme.surface.copy(alpha = alpha)
|
||||||
}
|
}
|
||||||
@@ -60,6 +60,19 @@ object GlassTopAppBarDefaults {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun containerColor(): Color {
|
||||||
|
val enableBlur = ThemeConfig.enableBlur
|
||||||
|
|
||||||
|
val containerColor = if (enableBlur) {
|
||||||
|
MaterialTheme.colorScheme.surface.copy(alpha = 0f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surface
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerColor
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun controlContainerColor(): Color {
|
fun controlContainerColor(): Color {
|
||||||
val enableBlur = ThemeConfig.enableBlur
|
val enableBlur = ThemeConfig.enableBlur
|
||||||
@@ -72,4 +85,4 @@ object GlassTopAppBarDefaults {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ import androidx.compose.material3.TooltipAnchorPosition
|
|||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.animateFloatingActionButton
|
import androidx.compose.material3.animateFloatingActionButton
|
||||||
import androidx.compose.material3.rememberTooltipState
|
import androidx.compose.material3.rememberTooltipState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -36,6 +37,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
import dev.chrisbanes.haze.HazeState
|
||||||
|
import io.legado.app.ui.theme.responsiveHazeEffect
|
||||||
|
import io.legado.app.ui.theme.responsiveHazeSource
|
||||||
import io.legado.app.ui.widget.components.SelectionActions
|
import io.legado.app.ui.widget.components.SelectionActions
|
||||||
import io.legado.app.ui.widget.components.SelectionBottomBar
|
import io.legado.app.ui.widget.components.SelectionBottomBar
|
||||||
import io.legado.app.ui.widget.components.rules.ListUiState
|
import io.legado.app.ui.widget.components.rules.ListUiState
|
||||||
@@ -46,13 +50,14 @@ import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
|
|||||||
fun <T> ListScaffold(
|
fun <T> ListScaffold(
|
||||||
title: String,
|
title: String,
|
||||||
state: ListUiState<T>,
|
state: ListUiState<T>,
|
||||||
onBackClick: () -> Unit,
|
subtitle: String? = null,
|
||||||
|
onBackClick: (() -> Unit)? = null,
|
||||||
onSearchToggle: (Boolean) -> Unit,
|
onSearchToggle: (Boolean) -> Unit,
|
||||||
onSearchQueryChange: (String) -> Unit,
|
onSearchQueryChange: (String) -> Unit,
|
||||||
searchPlaceholder: String = "搜索...",
|
searchPlaceholder: String = "搜索...",
|
||||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
bottomContent: @Composable (ColumnScope.() -> Unit)? = null,
|
bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null,
|
||||||
dropDownMenuContent: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit = {},
|
dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
|
||||||
selectionActions: SelectionActions? = null,
|
selectionActions: SelectionActions? = null,
|
||||||
onAddClick: (() -> Unit)? = null,
|
onAddClick: (() -> Unit)? = null,
|
||||||
floatingActionButton: @Composable () -> Unit = {
|
floatingActionButton: @Composable () -> Unit = {
|
||||||
@@ -80,28 +85,36 @@ fun <T> ListScaffold(
|
|||||||
content: @Composable (PaddingValues) -> Unit
|
content: @Composable (PaddingValues) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
val hazeState = remember { HazeState() }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
DynamicTopAppBar(
|
Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) {
|
||||||
title = title,
|
DynamicTopAppBar(
|
||||||
state = state,
|
title = title,
|
||||||
scrollBehavior = scrollBehavior,
|
subtitle = subtitle,
|
||||||
onBackClick = onBackClick,
|
state = state,
|
||||||
onSearchToggle = onSearchToggle,
|
scrollBehavior = scrollBehavior,
|
||||||
onSearchQueryChange = onSearchQueryChange,
|
onBackClick = onBackClick,
|
||||||
searchPlaceholder = searchPlaceholder,
|
onSearchToggle = onSearchToggle,
|
||||||
onClearSelection = { selectionActions?.onSelectInvert?.invoke() }, // 或其他逻辑
|
onSearchQueryChange = onSearchQueryChange,
|
||||||
topBarActions = topBarActions,
|
searchPlaceholder = searchPlaceholder,
|
||||||
dropDownMenuContent = dropDownMenuContent,
|
onClearSelection = { selectionActions?.onSelectInvert?.invoke() },
|
||||||
bottomContent = bottomContent
|
topBarActions = topBarActions,
|
||||||
)
|
dropDownMenuContent = dropDownMenuContent,
|
||||||
|
bottomContent = bottomContent
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = floatingActionButton
|
floatingActionButton = floatingActionButton
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.responsiveHazeSource(hazeState)
|
||||||
|
) {
|
||||||
content(paddingValues)
|
content(paddingValues)
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
@@ -124,4 +137,4 @@ fun <T> ListScaffold(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+84
-11
@@ -1,31 +1,104 @@
|
|||||||
package io.legado.app.ui.widget.components.menuItem
|
package io.legado.app.ui.widget.components.menuItem
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MenuDefaults
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.graphics.Color
|
||||||
|
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.rememberOpaqueColorScheme
|
import io.legado.app.ui.theme.rememberOpaqueColorScheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoundDropdownMenuItem(
|
fun RoundDropdownMenuItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
text: @Composable () -> Unit,
|
text: @Composable () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
|
||||||
|
interactionSource: MutableInteractionSource? = null,
|
||||||
) {
|
) {
|
||||||
val colorScheme = rememberOpaqueColorScheme()
|
val colorScheme = rememberOpaqueColorScheme()
|
||||||
|
val interaction = interactionSource ?: remember { MutableInteractionSource() }
|
||||||
|
|
||||||
DropdownMenuItem(
|
val contentColor =
|
||||||
|
if (enabled) MaterialTheme.colorScheme.onSurface
|
||||||
|
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
onClick = onClick,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.clip(MaterialTheme.shapes.small)
|
.fillMaxWidth(),
|
||||||
.background(colorScheme.surface),
|
enabled = enabled,
|
||||||
text = text,
|
shape = MaterialTheme.shapes.small,
|
||||||
trailingIcon = trailingIcon,
|
color = colorScheme.surface,
|
||||||
onClick = onClick
|
interactionSource = interaction
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides contentColor,
|
||||||
|
LocalTextStyle provides MaterialTheme.typography.labelLarge
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(contentPadding)
|
||||||
|
.heightIn(min = 48.dp)
|
||||||
|
.widthIn(min = 120.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (leadingIcon != null) {
|
||||||
|
leadingIcon()
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
text()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trailingIcon != null) {
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
trailingIcon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MenuItemIcon(
|
||||||
|
imageVector: ImageVector,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
contentDescription: String? = null,
|
||||||
|
tint: Color = LocalContentColor.current
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = imageVector,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier.size(18.dp),
|
||||||
|
tint = tint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,8 @@
|
|||||||
package io.legado.app.ui.widget.components.rules
|
package io.legado.app.ui.widget.components.rules
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutVertically
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
@@ -20,20 +11,17 @@ import androidx.compose.material3.ButtonDefaults
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.PlainTooltip
|
import androidx.compose.material3.PlainTooltip
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TooltipAnchorPosition
|
import androidx.compose.material3.TooltipAnchorPosition
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.animateFloatingActionButton
|
import androidx.compose.material3.animateFloatingActionButton
|
||||||
import androidx.compose.material3.rememberTooltipState
|
import androidx.compose.material3.rememberTooltipState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -43,27 +31,25 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.ui.widget.components.ActionItem
|
import io.legado.app.ui.widget.components.ActionItem
|
||||||
import io.legado.app.ui.widget.components.SelectionBottomBar
|
import io.legado.app.ui.widget.components.SelectionActions
|
||||||
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
|
import io.legado.app.ui.widget.components.list.ListScaffold
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> RuleListScaffold(
|
fun <T> RuleListScaffold(
|
||||||
title: String,
|
title: String,
|
||||||
state: ListUiState<T>,
|
state: ListUiState<T>,
|
||||||
|
subtitle: String? = null,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onSearchToggle: (Boolean) -> Unit,
|
onSearchToggle: (Boolean) -> Unit,
|
||||||
onSearchQueryChange: (String) -> Unit,
|
onSearchQueryChange: (String) -> Unit,
|
||||||
searchPlaceholder: String = "搜索...",
|
searchPlaceholder: String = "搜索...",
|
||||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
bottomContent: @Composable (ColumnScope.() -> Unit)? = null,
|
bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null,
|
||||||
dropDownMenuContent: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit = {},
|
dropDownMenuContent: (@Composable ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
|
||||||
onClearSelection: () -> Unit,
|
onClearSelection: () -> Unit,
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
onSelectInvert: () -> Unit,
|
onSelectInvert: () -> Unit,
|
||||||
@@ -94,8 +80,6 @@ fun <T> RuleListScaffold(
|
|||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
content: @Composable (PaddingValues) -> Unit
|
content: @Composable (PaddingValues) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
|
||||||
var showDeleteConfirmDialog by remember { mutableStateOf(false) }
|
var showDeleteConfirmDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showDeleteConfirmDialog) {
|
if (showDeleteConfirmDialog) {
|
||||||
@@ -124,50 +108,30 @@ fun <T> RuleListScaffold(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
ListScaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
title = title,
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
subtitle = subtitle,
|
||||||
topBar = {
|
state = state,
|
||||||
DynamicTopAppBar(
|
onBackClick = onBackClick,
|
||||||
title = title,
|
onSearchToggle = onSearchToggle,
|
||||||
state = state,
|
onSearchQueryChange = onSearchQueryChange,
|
||||||
scrollBehavior = scrollBehavior,
|
searchPlaceholder = searchPlaceholder,
|
||||||
onBackClick = onBackClick,
|
topBarActions = topBarActions,
|
||||||
onSearchToggle = onSearchToggle,
|
bottomContent = bottomContent,
|
||||||
onSearchQueryChange = onSearchQueryChange,
|
dropDownMenuContent = dropDownMenuContent,
|
||||||
searchPlaceholder = searchPlaceholder,
|
onAddClick = onAddClick,
|
||||||
onClearSelection = onClearSelection,
|
|
||||||
topBarActions = topBarActions,
|
|
||||||
dropDownMenuContent = dropDownMenuContent,
|
|
||||||
bottomContent = bottomContent
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = floatingActionButton,
|
floatingActionButton = floatingActionButton,
|
||||||
content = { paddingValues ->
|
snackbarHostState = snackbarHostState,
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
selectionActions = SelectionActions(
|
||||||
content(paddingValues)
|
onSelectAll = onSelectAll,
|
||||||
AnimatedVisibility(
|
onSelectInvert = onSelectInvert,
|
||||||
visible = state.selectedIds.isNotEmpty(),
|
primaryAction = ActionItem(
|
||||||
modifier = Modifier
|
text = stringResource(R.string.delete),
|
||||||
.align(Alignment.BottomCenter)
|
icon = { Icon(Icons.Default.Delete, null) },
|
||||||
.offset(y = -ScreenOffset)
|
onClick = { showDeleteConfirmDialog = true }
|
||||||
.padding(bottom = 16.dp)
|
),
|
||||||
.zIndex(1f),
|
secondaryActions = selectionSecondaryActions
|
||||||
enter = slideInVertically { it } + fadeIn(),
|
),
|
||||||
exit = slideOutVertically { it } + fadeOut()
|
content = content
|
||||||
) {
|
|
||||||
SelectionBottomBar(
|
|
||||||
onSelectAll = onSelectAll,
|
|
||||||
onSelectInvert = onSelectInvert,
|
|
||||||
primaryAction = ActionItem(
|
|
||||||
text = stringResource(R.string.delete),
|
|
||||||
icon = { Icon(Icons.Default.Delete, null) },
|
|
||||||
onClick = { showDeleteConfirmDialog = true }
|
|
||||||
),
|
|
||||||
secondaryActions = selectionSecondaryActions
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,13 @@ package io.legado.app.ui.widget.components.settingItem
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DropdownListSettingItem(
|
fun DropdownListSettingItem(
|
||||||
@@ -31,7 +31,7 @@ fun DropdownListSettingItem(
|
|||||||
onClick = { },
|
onClick = { },
|
||||||
dropdownMenu = { onDismiss ->
|
dropdownMenu = { onDismiss ->
|
||||||
displayEntries.forEachIndexed { index, display ->
|
displayEntries.forEachIndexed { index, display ->
|
||||||
DropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = { Text(display) },
|
text = { Text(display) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onValueChange(entryValues[index])
|
onValueChange(entryValues[index])
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
@@ -30,10 +28,12 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.legado.app.ui.widget.components.card.GlassCard
|
import io.legado.app.ui.widget.components.card.GlassCard
|
||||||
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -42,6 +42,7 @@ fun SettingItem(
|
|||||||
painter: Painter? = null,
|
painter: Painter? = null,
|
||||||
imageVector: ImageVector? = null,
|
imageVector: ImageVector? = null,
|
||||||
color: Color? = null,
|
color: Color? = null,
|
||||||
|
shape: Shape = MaterialTheme.shapes.extraSmall,
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
option: String? = null,
|
option: String? = null,
|
||||||
@@ -58,14 +59,14 @@ fun SettingItem(
|
|||||||
val isExpandable = expandContent != null && onExpandChange != null
|
val isExpandable = expandContent != null && onExpandChange != null
|
||||||
|
|
||||||
GlassCard(
|
GlassCard(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(4.dp),
|
shape = shape,
|
||||||
color = color ?: MaterialTheme.colorScheme.surfaceContainerLow,
|
color = color ?: MaterialTheme.colorScheme.surfaceContainerLow,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
when {
|
when {
|
||||||
@@ -139,7 +140,7 @@ fun SettingItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
dropdownMenu?.let { menu ->
|
dropdownMenu?.let { menu ->
|
||||||
DropdownMenu(
|
RoundDropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onDismissRequest = { showMenu = false }) {
|
onDismissRequest = { showMenu = false }) {
|
||||||
menu { showMenu = false }
|
menu { showMenu = false }
|
||||||
@@ -167,4 +168,4 @@ fun SettingItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,6 @@ import androidx.compose.material.icons.filled.MoreVert
|
|||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -29,6 +28,7 @@ import io.legado.app.ui.widget.components.AdaptiveAnimatedText
|
|||||||
import io.legado.app.ui.widget.components.AnimatedTextLine
|
import io.legado.app.ui.widget.components.AnimatedTextLine
|
||||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||||
import io.legado.app.ui.widget.components.SearchBarSection
|
import io.legado.app.ui.widget.components.SearchBarSection
|
||||||
|
import io.legado.app.ui.widget.components.button.TopBarActionButton
|
||||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||||
import io.legado.app.ui.widget.components.rules.ListUiState
|
import io.legado.app.ui.widget.components.rules.ListUiState
|
||||||
@@ -41,7 +41,7 @@ fun <T> DynamicTopAppBar(
|
|||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
state: ListUiState<T>,
|
state: ListUiState<T>,
|
||||||
scrollBehavior: TopAppBarScrollBehavior,
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: (() -> Unit)? = null,
|
||||||
onSearchToggle: (Boolean) -> Unit,
|
onSearchToggle: (Boolean) -> Unit,
|
||||||
onSearchQueryChange: (String) -> Unit,
|
onSearchQueryChange: (String) -> Unit,
|
||||||
searchPlaceholder: String,
|
searchPlaceholder: String,
|
||||||
@@ -51,7 +51,7 @@ fun <T> DynamicTopAppBar(
|
|||||||
onClearSelection: () -> Unit,
|
onClearSelection: () -> Unit,
|
||||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
|
dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
|
||||||
bottomContent: @Composable (ColumnScope.() -> Unit)? = null
|
bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
val isSelecting = state.selectedIds.isNotEmpty()
|
val isSelecting = state.selectedIds.isNotEmpty()
|
||||||
@@ -75,25 +75,31 @@ fun <T> DynamicTopAppBar(
|
|||||||
{ AnimatedTextLine(text = it) }
|
{ AnimatedTextLine(text = it) }
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
TopbarNavigationButton(
|
if (isSelecting || onBackClick != null) {
|
||||||
onClick = { if (isSelecting) onClearSelection() else onBackClick() },
|
TopbarNavigationButton(
|
||||||
imageVector = if (isSelecting) Icons.Default.Close else Icons.AutoMirrored.Filled.ArrowBack,
|
onClick = { if (isSelecting) onClearSelection() else onBackClick?.invoke() },
|
||||||
contentDescription = if (isSelecting) "取消选择" else "返回"
|
imageVector = if (isSelecting) Icons.Default.Close else Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
)
|
contentDescription = if (isSelecting) "取消选择" else "返回"
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
if (!isSelecting) {
|
if (!isSelecting) {
|
||||||
IconButton(onClick = { onSearchToggle(!state.isSearch) }) {
|
TopBarActionButton(
|
||||||
Icon(Icons.Default.Search, null)
|
onClick = { onSearchToggle(!state.isSearch) },
|
||||||
}
|
imageVector = Icons.Default.Search,
|
||||||
|
contentDescription = "搜索"
|
||||||
|
)
|
||||||
|
|
||||||
topBarActions()
|
topBarActions()
|
||||||
|
|
||||||
dropDownMenuContent?.let { content ->
|
dropDownMenuContent?.let { content ->
|
||||||
Box {
|
Box {
|
||||||
IconButton(onClick = { showMenu = true }) {
|
TopBarActionButton(
|
||||||
Icon(Icons.Default.MoreVert, null)
|
onClick = { showMenu = true },
|
||||||
}
|
imageVector = Icons.Default.MoreVert,
|
||||||
|
contentDescription = "更多"
|
||||||
|
)
|
||||||
RoundDropdownMenu(
|
RoundDropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onDismissRequest = { showMenu = false }
|
onDismissRequest = { showMenu = false }
|
||||||
@@ -122,6 +128,6 @@ fun <T> DynamicTopAppBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
bottomContent?.invoke(this)
|
bottomContent?.invoke(this, scrollBehavior)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,7 +755,7 @@
|
|||||||
<string name="default_cover">默认封面</string>
|
<string name="default_cover">默认封面</string>
|
||||||
<string name="restore_ignore">恢复忽略列表</string>
|
<string name="restore_ignore">恢复忽略列表</string>
|
||||||
<string name="restore_ignore_summary">选择恢复忽略内容</string>
|
<string name="restore_ignore_summary">选择恢复忽略内容</string>
|
||||||
<string name="read_config">界面设置</string>
|
<string name="read_config">阅读界面</string>
|
||||||
<string name="group_name">分组名称</string>
|
<string name="group_name">分组名称</string>
|
||||||
<string name="note_content">备注</string>
|
<string name="note_content">备注</string>
|
||||||
<string name="replace_enable_default_t">默认启用替换净化</string>
|
<string name="replace_enable_default_t">默认启用替换净化</string>
|
||||||
|
|||||||
Reference in new issue
Block a user