[优化] 增加控件

This commit is contained in:
HapeLee committed 2026-04-03 01:08:51 +08:00
1 parent 733312fe23
commit dd88417f8a
81 files changed
+1754 -1001

No files matched your search

@@ -15,7 +15,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -91,8 +90,6 @@ fun AboutScreen(
.wrapContentWidth(Alignment.CenterHorizontally)) .wrapContentWidth(Alignment.CenterHorizontally))
TextCard( TextCard(
text = versionName, text = versionName,
backgroundColor = colorScheme.tertiaryContainer,
contentColor = colorScheme.onTertiaryContainer,
cornerRadius = 8.dp, cornerRadius = 8.dp,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
import java.util.Date import java.util.Date
@@ -39,12 +39,14 @@ import java.util.Date
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AppLogSheet( fun AppLogSheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
var logs by remember { mutableStateOf(AppLog.logs) } var logs by remember { mutableStateOf(AppLog.logs) }
var showStackTrace by remember { mutableStateOf<String?>(null) } var showStackTrace by remember { mutableStateOf<String?>(null) }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Row( Row(
@@ -48,7 +48,6 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.lazylist.Scroller import io.legado.app.ui.widget.components.lazylist.Scroller
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -128,7 +127,7 @@ fun AllBookmarkScreen(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("导出 JSON") }, text = "导出 JSON",
onClick = { onClick = {
showMenu = false showMenu = false
pendingExportIsMd = false pendingExportIsMd = false
@@ -136,7 +135,7 @@ fun AllBookmarkScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("导出 Markdown") }, text = "导出 Markdown",
onClick = { onClick = {
showMenu = false showMenu = false
pendingExportIsMd = true pendingExportIsMd = true
@@ -238,9 +237,9 @@ fun AllBookmarkScreen(
} }
} }
if (showBottomSheet && editingBookmark != null) {
BookmarkEditSheet( BookmarkEditSheet(
bookmark = editingBookmark!!, show = showBottomSheet && editingBookmark != null,
bookmark = editingBookmark ?: Bookmark(),
onDismiss = { onDismiss = {
showBottomSheet = false showBottomSheet = false
editingBookmark = null editingBookmark = null
@@ -256,4 +255,3 @@ fun AllBookmarkScreen(
) )
} }
} }
}
@@ -90,7 +90,7 @@ import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -180,9 +180,9 @@ fun ExploreShowScreen(
} }
} }
if (showGridCountSheet) {
GlassModalBottomSheet( AppModalBottomSheet(
show = showGridCountSheet,
modifier = Modifier modifier = Modifier
.padding(16.dp), .padding(16.dp),
onDismissRequest = { showGridCountSheet = false } onDismissRequest = { showGridCountSheet = false }
@@ -232,10 +232,10 @@ fun ExploreShowScreen(
AppText("完成") AppText("完成")
} }
} }
}
if (showKindSheet) {
GlassModalBottomSheet( AppModalBottomSheet(
show = showKindSheet,
onDismissRequest = { showKindSheet = false } onDismissRequest = { showKindSheet = false }
) { ) {
@@ -282,9 +282,8 @@ fun ExploreShowScreen(
) )
} }
} }
}
}
}
AppScaffold( AppScaffold(
modifier = Modifier modifier = Modifier
@@ -339,7 +338,7 @@ fun ExploreShowScreen(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("全部显示") }, text = "全部显示",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.SHOW_ALL) viewModel.setFilterState(BookFilterState.SHOW_ALL)
showMenu = false showMenu = false
@@ -351,7 +350,7 @@ fun ExploreShowScreen(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("隐藏已在书架的同源书籍") }, text = "隐藏已在书架的同源书籍",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF) viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
showMenu = false showMenu = false
@@ -363,7 +362,7 @@ fun ExploreShowScreen(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("隐藏已在书架的非同源书籍") }, text = "隐藏已在书架的非同源书籍",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR) viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
showMenu = false showMenu = false
@@ -375,7 +374,7 @@ fun ExploreShowScreen(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("只显示不在书架的书籍") }, text = "只显示不在书架的书籍",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY) viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
showMenu = false showMenu = false
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@@ -36,9 +35,8 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -55,11 +53,12 @@ import java.io.FileOutputStream
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupEditSheet( fun GroupEditSheet(
show: Boolean,
group: BookGroup? = null, group: BookGroup? = null,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: GroupViewModel = koinViewModel() viewModel: GroupViewModel = koinViewModel()
) { ) {
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
GroupEditContent( GroupEditContent(
group = group, group = group,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
@@ -108,17 +107,9 @@ fun GroupEditContent(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 16.dp)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
AppText(
text = stringResource(R.string.group_edit),
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp)
)
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
@@ -88,7 +88,7 @@ import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.list.ListScaffold import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.ConvertUtils import io.legado.app.utils.ConvertUtils
@@ -199,8 +199,8 @@ fun RemoteBookScreen(
} }
} }
if (showSheet != null) { AppModalBottomSheet(
GlassModalBottomSheet( show = showSheet != null,
onDismissRequest = { showSheet = null } onDismissRequest = { showSheet = null }
) { ) {
when (val state = showSheet) { when (val state = showSheet) {
@@ -236,7 +236,6 @@ fun RemoteBookScreen(
else -> {} else -> {}
} }
} }
}
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
viewModel.initData { viewModel.loadRemoteBookList() } viewModel.initData { viewModel.loadRemoteBookList() }
@@ -260,7 +259,7 @@ fun RemoteBookScreen(
}, },
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("按名称排序") }, text = "按名称排序",
onClick = { onClick = {
viewModel.toggleSort(RemoteBookSort.Name) viewModel.toggleSort(RemoteBookSort.Name)
dismiss() dismiss()
@@ -272,7 +271,7 @@ fun RemoteBookScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("按时间排序") }, text = "按时间排序",
onClick = { onClick = {
viewModel.toggleSort(RemoteBookSort.Default) viewModel.toggleSort(RemoteBookSort.Default)
dismiss() dismiss()
@@ -32,13 +32,9 @@ import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Timeline import androidx.compose.material.icons.filled.Timeline
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
@@ -78,13 +74,14 @@ import cn.hutool.core.date.DateUtil
import io.legado.app.data.entities.readRecord.ReadRecord import io.legado.app.data.entities.readRecord.ReadRecord
import io.legado.app.data.entities.readRecord.ReadRecordDetail import io.legado.app.data.entities.readRecord.ReadRecordDetail
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AnimatedTextLine import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBarSection
import io.legado.app.ui.widget.components.SectionHeader
import io.legado.app.ui.widget.components.button.AlertButton import io.legado.app.ui.widget.components.button.AlertButton
import io.legado.app.ui.widget.components.button.AppIconButton
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.card.GlassCard
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar
import io.legado.app.ui.widget.components.heatmap.HeatmapConfig import io.legado.app.ui.widget.components.heatmap.HeatmapConfig
@@ -96,9 +93,11 @@ import io.legado.app.ui.widget.components.heatmap.WeekdayLabelsColumn
import io.legado.app.ui.widget.components.heatmap.rememberDateRange import io.legado.app.ui.widget.components.heatmap.rememberDateRange
import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange
import io.legado.app.ui.widget.components.heatmap.rememberWeeks import io.legado.app.ui.widget.components.heatmap.rememberWeeks
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.swipe.SwipeAction import io.legado.app.ui.widget.components.swipe.SwipeAction
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
import io.legado.app.ui.widget.components.text.AnimatedTextLine
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -164,7 +163,7 @@ fun ReadRecordScreen(
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
}, },
actions = { actions = {
IconButton(onClick = { AppIconButton(onClick = {
val newMode = when (displayMode) { val newMode = when (displayMode) {
DisplayMode.AGGREGATE -> DisplayMode.TIMELINE DisplayMode.AGGREGATE -> DisplayMode.TIMELINE
DisplayMode.TIMELINE -> DisplayMode.LATEST DisplayMode.TIMELINE -> DisplayMode.LATEST
@@ -178,13 +177,16 @@ fun ReadRecordScreen(
DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List
} }
val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate" val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate"
Icon(icon, description) AppIcon(icon, description)
} }
IconButton(onClick = { showCalendar = !showCalendar }) { AppIconButton(onClick = { showCalendar = !showCalendar }) {
Icon(Icons.Default.CalendarMonth, contentDescription = "Toggle Calendar") AppIcon(
Icons.Default.CalendarMonth,
contentDescription = "Toggle Calendar"
)
} }
IconButton(onClick = { showSearch = !showSearch }) { AppIconButton(onClick = { showSearch = !showSearch }) {
Icon(Icons.Default.Search, contentDescription = null) AppIcon(Icons.Default.Search, contentDescription = null)
} }
}, },
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
@@ -314,8 +316,8 @@ fun ReadRecordScreen(
) )
} }
if (showCalendar) { AppModalBottomSheet(
GlassModalBottomSheet( show = showCalendar,
onDismissRequest = { showCalendar = false } onDismissRequest = { showCalendar = false }
) { ) {
HeatmapCalendarSection( HeatmapCalendarSection(
@@ -332,7 +334,6 @@ fun ReadRecordScreen(
} }
) )
} }
}
mergeDialogData?.let { (targetRecord, candidates) -> mergeDialogData?.let { (targetRecord, candidates) ->
@@ -869,28 +870,27 @@ fun DateHeader(
date: String, date: String,
dailyTotalTime: Long? = null dailyTotalTime: Long? = null
) { ) {
val dateText = formatFriendlyDate(date) CollapsibleHeader(
SectionHeader( showIcon = false,
isCollapsed = false,
onToggle = { },
title = "",
titleContent = { titleContent = {
Column { val dateText = formatFriendlyDate(date)
AppText( AppText(
text = dateText, text = dateText,
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.secondary color = LegadoTheme.colorScheme.primary
) )
dailyTotalTime?.let { total -> dailyTotalTime?.let { total ->
AppText( AppText(
text = "已读 ${formatDuring(total)}", text = "已读 ${formatDuring(total)}",
style = LegadoTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface color = LegadoTheme.colorScheme.onSurface
) )
} }
} }
},
detailContent = null,
horizontalArrangement = Arrangement.Start
) )
} }
@@ -912,14 +912,12 @@ fun ReadingSummaryCard(
val totalDurationMinutes = totalTimeMillis / 60000 val totalDurationMinutes = totalTimeMillis / 60000
Card( GlassCard(
onClick = onClick, onClick = onClick,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
colors = CardDefaults.cardColors( containerColor = LegadoTheme.colorScheme.surfaceContainer
containerColor = MaterialTheme.colorScheme.surfaceContainer
)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -934,7 +932,7 @@ fun ReadingSummaryCard(
AppText( AppText(
text = title, text = title,
style = LegadoTheme.typography.labelLarge, style = LegadoTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary color = LegadoTheme.colorScheme.primary
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -947,7 +945,7 @@ fun ReadingSummaryCard(
AppText( AppText(
text = "$bookCount", text = "$bookCount",
style = LegadoTheme.typography.headlineMedium, style = LegadoTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary, color = LegadoTheme.colorScheme.primary,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
AppText( AppText(
@@ -965,7 +963,7 @@ fun ReadingSummaryCard(
AppText( AppText(
text = "共阅读 $timeString", text = "共阅读 $timeString",
style = LegadoTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -310,7 +310,7 @@ fun SearchHistoryList(
AppText( AppText(
text = "搜索历史", text = "搜索历史",
style = LegadoTheme.typography.titleSmallEmphasized, style = LegadoTheme.typography.titleSmallEmphasized,
color = MaterialTheme.colorScheme.primary, color = LegadoTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
SmallAnimatedActionButton( SmallAnimatedActionButton(
@@ -313,33 +313,33 @@ fun TocScreen(
when (pagerState.currentPage) { when (pagerState.currentPage) {
0 -> { 0 -> {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("使用替换规则") }, text = "使用替换规则",
trailingIcon = { trailingIcon = {
Checkbox(checked = useReplace, onCheckedChange = null) Checkbox(checked = useReplace, onCheckedChange = null)
}, },
onClick = { viewModel.toggleUseReplace() } onClick = { viewModel.toggleUseReplace() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("显示字数") }, text = "显示字数",
trailingIcon = { trailingIcon = {
Checkbox(checked = showWordCount, onCheckedChange = null) Checkbox(checked = showWordCount, onCheckedChange = null)
}, },
onClick = { viewModel.toggleShowWordCount() } onClick = { viewModel.toggleShowWordCount() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("反转目录") }, text = "反转目录",
onClick = { viewModel.reverseToc() } onClick = { viewModel.reverseToc() }
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("替换规则") }, text = "替换规则",
onClick = { onClick = {
onOpenReplaceRule(null) onOpenReplaceRule(null)
dismiss() dismiss()
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("新建替换规则") }, text = "新建替换规则",
onClick = { onClick = {
val scopes = mutableListOf<String>() val scopes = mutableListOf<String>()
book?.name?.let { scopes.add(it) } book?.name?.let { scopes.add(it) }
@@ -359,7 +359,7 @@ fun TocScreen(
if (book?.isLocal == true) { if (book?.isLocal == true) {
PillHeaderDivider(title = "本地书籍选项") PillHeaderDivider(title = "本地书籍选项")
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("本地书籍目录规则") }, text = "本地书籍目录规则",
onClick = { onClick = {
val intent = val intent =
Intent(context, TxtTocRuleActivity::class.java).apply { Intent(context, TxtTocRuleActivity::class.java).apply {
@@ -370,7 +370,7 @@ fun TocScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("拆分超长章节") }, text = "拆分超长章节",
trailingIcon = { trailingIcon = {
Checkbox( Checkbox(
checked = viewModel.isSplitLongChapter, checked = viewModel.isSplitLongChapter,
@@ -387,7 +387,7 @@ fun TocScreen(
else -> { else -> {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("导出书签为JSON") }, text = "导出书签为JSON",
onClick = { onClick = {
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm", "yyyyMMdd_HHmm",
@@ -399,7 +399,7 @@ fun TocScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("导出书签为MarkDown") }, text = "导出书签为MarkDown",
onClick = { onClick = {
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm", "yyyyMMdd_HHmm",
@@ -465,13 +465,13 @@ fun TocScreen(
onDismissRequest = { showVolumeMenu = false } onDismissRequest = { showVolumeMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("展开所有卷") }, text = "展开所有卷",
onClick = { onClick = {
viewModel.expandAllVolumes(); showVolumeMenu = false viewModel.expandAllVolumes(); showVolumeMenu = false
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("收起所有卷") }, text = "收起所有卷",
onClick = { onClick = {
viewModel.collapseAllVolumes(); showVolumeMenu = false viewModel.collapseAllVolumes(); showVolumeMenu = false
} }
@@ -483,7 +483,7 @@ fun TocScreen(
PillHeaderDivider(title = "快速跳转") PillHeaderDivider(title = "快速跳转")
volumeItems.forEach { uiItem -> volumeItems.forEach { uiItem ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(uiItem.title) }, text = uiItem.title,
onClick = { onClick = {
scope.launch { scope.launch {
val targetIndex = val targetIndex =
@@ -594,8 +594,8 @@ fun TocScreen(
} }
} }
if (editingBookmark != null) {
BookmarkEditSheet( BookmarkEditSheet(
show = editingBookmark != null,
bookmark = editingBookmark!!, bookmark = editingBookmark!!,
onDismiss = { editingBookmark = null }, onDismiss = { editingBookmark = null },
onSave = { updatedBookmark -> onSave = { updatedBookmark ->
@@ -609,7 +609,6 @@ fun TocScreen(
) )
} }
} }
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.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
@@ -44,13 +43,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseRuleEvent import io.legado.app.base.BaseRuleEvent
import io.legado.app.data.entities.TxtTocRule import io.legado.app.data.entities.TxtTocRule
import io.legado.app.ui.theme.LegadoTheme
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.button.SmallIconButton import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -154,8 +151,8 @@ fun TxtRuleScreen(
) )
} }
if (showExportSheet) {
FilePickerSheet( FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export), title = stringResource(R.string.export),
onSelectSysDir = { onSelectSysDir = {
@@ -168,10 +165,10 @@ fun TxtRuleScreen(
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
}
if (showImportSheet) {
FilePickerSheet( FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false }, onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_txt_toc_rule), title = stringResource(R.string.import_txt_toc_rule),
onSelectSysFile = { types -> onSelectSysFile = { types ->
@@ -184,24 +181,19 @@ fun TxtRuleScreen(
}, },
allowExtensions = arrayOf("json", "txt") allowExtensions = arrayOf("json", "txt")
) )
}
(importState as? BaseImportUiState.Success<TxtTocRule>)?.let { state ->
BatchImportDialog( BatchImportDialog(
title = "导入词典规则", title = "导入词典规则",
importState = state, importState = importState,
onDismissRequest = { viewModel.cancelImport() }, onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) }, onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) }, onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() }, onConfirm = { viewModel.saveImportedRules() },
itemContent = { rule, _ -> itemTitle = { rule -> rule.name },
Column { itemSubtitle = { rule ->
AppText(rule.name, style = LegadoTheme.typography.titleMedium) rule.rule.takeIf { it.isNotBlank() }
AppText(rule.rule, style = LegadoTheme.typography.bodySmall, maxLines = 1)
}
} }
) )
}
LaunchedEffect(reorderableState.isAnyItemDragging) { LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) {
@@ -229,8 +221,8 @@ fun TxtRuleScreen(
) )
} }
if (showEditSheet) {
RuleEditSheet( RuleEditSheet(
show = showEditSheet,
rule = editingRule, rule = editingRule,
title = stringResource(R.string.txt_toc_rule), title = stringResource(R.string.txt_toc_rule),
label1 = stringResource(R.string.regex), label1 = stringResource(R.string.regex),
@@ -270,7 +262,6 @@ fun TxtRuleScreen(
) )
} }
) )
}
RuleListScaffold( RuleListScaffold(
title = if (isPickMode) "选择目录规则" else "目录规则", title = if (isPickMode) "选择目录规则" else "目录规则",
@@ -312,7 +303,7 @@ fun TxtRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_str)) }, text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
@@ -453,8 +453,8 @@ fun BackupConfigScreen(
) )
} }
if (showBackupFilePicker) {
FilePickerSheet( FilePickerSheet(
show = showBackupFilePicker,
onDismissRequest = { showBackupFilePicker = false }, onDismissRequest = { showBackupFilePicker = false },
onSelectSysDir = { onSelectSysDir = {
showBackupFilePicker = false showBackupFilePicker = false
@@ -464,10 +464,10 @@ fun BackupConfigScreen(
} }
} }
) )
}
if (showRestoreFilePicker) {
FilePickerSheet( FilePickerSheet(
show = showRestoreFilePicker,
onDismissRequest = { showRestoreFilePicker = false }, onDismissRequest = { showRestoreFilePicker = false },
onSelectSysDir = { onSelectSysDir = {
showRestoreFilePicker = false showRestoreFilePicker = false
@@ -477,7 +477,6 @@ fun BackupConfigScreen(
} }
} }
) )
}
if (showRestoreSheet && backupNames.isNotEmpty()) { if (showRestoreSheet && backupNames.isNotEmpty()) {
AlertDialog( AlertDialog(
@@ -246,17 +246,17 @@ fun CoverConfigScreen(
} }
} }
if (showCoverRuleSheet) { CoverRuleConfigSheet(
CoverRuleConfigSheet(onDismissRequest = { showCoverRuleSheet = false }) show = showCoverRuleSheet,
} onDismissRequest = { showCoverRuleSheet = false }
)
manageKey?.let { key ->
CoverManageSheet( CoverManageSheet(
preferenceKey = key, show = manageKey != null,
preferenceKey = manageKey!!,
onDismissRequest = { manageKey = null }, onDismissRequest = { manageKey = null },
viewModel = viewModel viewModel = viewModel
) )
}
showColorPickerByField?.let { field -> showColorPickerByField?.let { field ->
val initialColor = when (field) { val initialColor = when (field) {
@@ -268,6 +268,7 @@ fun CoverConfigScreen(
} }
ColorPickerSheet( ColorPickerSheet(
show = showColorPickerByField != null,
initialColor = initialColor, initialColor = initialColor,
onDismissRequest = { showColorPickerByField = null }, onDismissRequest = { showColorPickerByField = null },
onColorSelected = { color -> onColorSelected = { color ->
@@ -37,12 +37,13 @@ import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CoverManageSheet( fun CoverManageSheet(
show: Boolean,
preferenceKey: String, preferenceKey: String,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: CoverConfigViewModel viewModel: CoverConfigViewModel
@@ -62,7 +63,8 @@ fun CoverManageSheet(
} }
val coverList = currentCovers.split(",").filter { it.isNotBlank() } val coverList = currentCovers.split(",").filter { it.isNotBlank() }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -142,8 +144,8 @@ fun CoverManageSheet(
} }
} }
if (showFilePicker) {
FilePickerSheet( FilePickerSheet(
show = showFilePicker,
onDismissRequest = { showFilePicker = false }, onDismissRequest = { showFilePicker = false },
onSelectSysFiles = { onSelectSysFiles = {
selectImages.launch("image/*") selectImages.launch("image/*")
@@ -152,4 +154,3 @@ fun CoverManageSheet(
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp") allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
) )
} }
}
@@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.model.BookCover import io.legado.app.model.BookCover
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.InputSettingItem import io.legado.app.ui.widget.components.settingItem.InputSettingItem
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -36,6 +36,7 @@ import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CoverRuleConfigSheet( fun CoverRuleConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
var enable by remember { mutableStateOf(false) } var enable by remember { mutableStateOf(false) }
@@ -51,7 +52,8 @@ fun CoverRuleConfigSheet(
coverRule = rule.coverRule coverRule = rule.coverRule
} }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import io.legado.app.ui.widget.components.text.AppText
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -21,19 +20,22 @@ import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CheckSourceBottomSheet( fun CheckSourceBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel = koinViewModel(), viewModel: OtherConfigViewModel = koinViewModel(),
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss onDismissRequest = onDismiss
) { ) {
Column( Column(
@@ -42,7 +42,7 @@ import io.legado.app.help.DirectLinkUpload
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
@@ -53,6 +53,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DirectLinkUploadBottomSheet( fun DirectLinkUploadBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel, viewModel: OtherConfigViewModel,
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
@@ -64,7 +65,8 @@ fun DirectLinkUploadBottomSheet(
viewModel.initDirectLinkRule() viewModel.initDirectLinkRule()
} }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss onDismissRequest = onDismiss
) { ) {
Column( Column(
@@ -398,8 +398,8 @@ fun OtherConfigScreen(
} }
} }
if (showFilePicker) {
FilePickerSheet( FilePickerSheet(
show = showFilePicker,
onDismissRequest = { showFilePicker = false }, onDismissRequest = { showFilePicker = false },
onSelectSysDir = { onSelectSysDir = {
showFilePicker = false showFilePicker = false
@@ -410,21 +410,18 @@ fun OtherConfigScreen(
} }
} }
) )
}
if (showCheckSourceSheet) {
CheckSourceBottomSheet( CheckSourceBottomSheet(
show = showCheckSourceSheet,
viewModel = viewModel, viewModel = viewModel,
onDismiss = { showCheckSourceSheet = false } onDismiss = { showCheckSourceSheet = false }
) )
}
if (showDirectLinkUploadSheet) {
DirectLinkUploadBottomSheet( DirectLinkUploadBottomSheet(
show = showDirectLinkUploadSheet,
viewModel = viewModel, viewModel = viewModel,
onDismiss = { showDirectLinkUploadSheet = false } onDismiss = { showDirectLinkUploadSheet = false }
) )
}
if (showClearCacheDialog) { if (showClearCacheDialog) {
ConfirmDialog( ConfirmDialog(
@@ -23,18 +23,19 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PageKeySheet( fun PageKeySheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) } var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) }
var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) } var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) }
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column( Column(
modifier = Modifier.Companion modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
@@ -320,7 +320,8 @@ fun ReadConfigScreen(
} }
} }
if (showPageKeySheet) { PageKeySheet(
PageKeySheet(onDismissRequest = { showPageKeySheet = false }) show = showPageKeySheet,
} onDismissRequest = { showPageKeySheet = false }
)
} }
@@ -32,7 +32,7 @@ import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -40,6 +40,7 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BackgroundImageManageSheet( fun BackgroundImageManageSheet(
show: Boolean,
isDarkTheme: Boolean, isDarkTheme: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: ThemeConfigViewModel = koinViewModel() viewModel: ThemeConfigViewModel = koinViewModel()
@@ -65,7 +66,8 @@ fun BackgroundImageManageSheet(
ThemeConfig.bgImageLight ThemeConfig.bgImageLight
} }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -140,8 +142,8 @@ fun BackgroundImageManageSheet(
} }
} }
if (showFilePicker) {
FilePickerSheet( FilePickerSheet(
show = showFilePicker,
onDismissRequest = { showFilePicker = false }, onDismissRequest = { showFilePicker = false },
onSelectSysFile = { onSelectSysFile = {
selectImage.launch("image/*") selectImage.launch("image/*")
@@ -150,4 +152,3 @@ fun BackgroundImageManageSheet(
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp") allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
) )
} }
}
@@ -39,7 +39,7 @@ import io.legado.app.ui.main.Launcher5
import io.legado.app.ui.main.Launcher6 import io.legado.app.ui.main.Launcher6
import io.legado.app.ui.main.LauncherW import io.legado.app.ui.main.LauncherW
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.getCompatDrawable import io.legado.app.utils.getCompatDrawable
import splitties.init.appCtx import splitties.init.appCtx
@@ -47,6 +47,7 @@ import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LauncherIconPickerSheet( fun LauncherIconPickerSheet(
show: Boolean,
selectedValue: String, selectedValue: String,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onValueChange: (String) -> Unit onValueChange: (String) -> Unit
@@ -54,7 +55,8 @@ fun LauncherIconPickerSheet(
val context = LocalContext.current val context = LocalContext.current
val icons = LauncherIcons.list val icons = LauncherIcons.list
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -15,14 +15,14 @@ object ThemeConfig {
var enableBlur by prefDelegate(PreferKey.enableBlur, false) var enableBlur by prefDelegate(PreferKey.enableBlur, false)
var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, true) var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, false)
var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true) var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true)
var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot") var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot")
//m3 or miuix //m3 or miuix
var composeEngine by prefDelegate(PreferKey.composeEngine, "m3") var composeEngine by prefDelegate(PreferKey.composeEngine, "material")
var materialVersion by prefDelegate(PreferKey.materialVersion, "material3") var materialVersion by prefDelegate(PreferKey.materialVersion, "material3")
@@ -15,6 +15,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -44,6 +45,7 @@ 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.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.ToggleButton import androidx.compose.material3.ToggleButton
import androidx.compose.material3.ToggleButtonDefaults import androidx.compose.material3.ToggleButtonDefaults
@@ -95,6 +97,10 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.restart import io.legado.app.utils.restart
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
@@ -137,9 +143,10 @@ fun ThemeConfigScreen(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(16.dp) .padding(16.dp)
) { ) {
val composeEngine = remember { ThemeConfig.composeEngine } val composeEngine = ThemeConfig.composeEngine
val isMiuixEngine = val isMiuixEngine = remember(composeEngine) {
remember(composeEngine) { ThemeResolver.isMiuixEngine(composeEngine) } ThemeResolver.isMiuixEngine(composeEngine)
}
val isDarkTheme = when (selectedThemeMode) { val isDarkTheme = when (selectedThemeMode) {
"1" -> false "1" -> false
"2" -> true "2" -> true
@@ -169,6 +176,20 @@ fun ThemeConfigScreen(
themeItems.zip(themeValues).toList() themeItems.zip(themeValues).toList()
} }
if (isMiuixEngine) {
MiuixCard(
cornerRadius = 16.dp,
insideMargin = PaddingValues(16.dp),
colors = MiuixCardDefaults.defaultColors(
color = MiuixTheme.colorScheme.primaryVariant,
contentColor = MiuixTheme.colorScheme.onPrimary
)
) {
MiuixText("Miuix 目前为测试主题,且不对基于View的界面生效!")
}
}
SplicedColumnGroup(title = stringResource(R.string.theme)) { SplicedColumnGroup(title = stringResource(R.string.theme)) {
if (isMiuixEngine) { if (isMiuixEngine) {
DropdownListSettingItem( DropdownListSettingItem(
@@ -253,7 +274,9 @@ fun ThemeConfigScreen(
selectedValue = ThemeConfig.composeEngine, selectedValue = ThemeConfig.composeEngine,
displayEntries = stringArrayResource(R.array.composeEngine), displayEntries = stringArrayResource(R.array.composeEngine),
entryValues = stringArrayResource(R.array.composeEngine_value), entryValues = stringArrayResource(R.array.composeEngine_value),
onValueChange = { ThemeConfig.composeEngine = it } onValueChange = {
ThemeConfig.composeEngine = it
}
) )
SliderSettingItem( SliderSettingItem(
title = stringResource(R.string.font_scale), title = stringResource(R.string.font_scale),
@@ -531,13 +554,15 @@ fun ThemeConfigScreen(
manageKey?.let { isDark -> manageKey?.let { isDark ->
BackgroundImageManageSheet( BackgroundImageManageSheet(
show = true,
isDarkTheme = isDark, isDarkTheme = isDark,
onDismissRequest = { manageKey = null } onDismissRequest = { manageKey = null }
) )
} }
if (showColorPicker) {
ColorPickerSheet( ColorPickerSheet(
show = showColorPicker,
initialColor = primaryColorValue.value, initialColor = primaryColorValue.value,
onDismissRequest = { showColorPicker = false }, onDismissRequest = { showColorPicker = false },
onColorSelected = { color -> onColorSelected = { color ->
@@ -554,10 +579,9 @@ fun ThemeConfigScreen(
) )
} }
) )
}
if (showLauncherIconPicker) {
LauncherIconPickerSheet( LauncherIconPickerSheet(
show = showLauncherIconPicker,
selectedValue = ThemeConfig.launcherIcon, selectedValue = ThemeConfig.launcherIcon,
onDismissRequest = { showLauncherIconPicker = false }, onDismissRequest = { showLauncherIconPicker = false },
onValueChange = { onValueChange = {
@@ -565,13 +589,11 @@ fun ThemeConfigScreen(
LauncherIconHelp.changeIcon(it) LauncherIconHelp.changeIcon(it)
} }
) )
}
if (showThemeListDialog) {
ThemeListDialog( ThemeListDialog(
show = showThemeListDialog,
onDismissRequest = { showThemeListDialog = false } onDismissRequest = { showThemeListDialog = false }
) )
}
saveThemeKey?.let { key -> saveThemeKey?.let { key ->
AlertDialog( AlertDialog(
@@ -652,7 +674,7 @@ fun ThemeModeSelector(
Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing)) Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
AppText(text = label) Text(text = label)
} }
} }
} }
@@ -38,7 +38,7 @@ import io.legado.app.help.config.OldThemeConfig
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
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.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.getClipText import io.legado.app.utils.getClipText
@@ -48,6 +48,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ThemeListDialog( fun ThemeListDialog(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -55,7 +56,8 @@ fun ThemeListDialog(
var deleteIndex by remember { mutableStateOf<Int?>(null) } var deleteIndex by remember { mutableStateOf<Int?>(null) }
val themeList = remember(listVersion) { OldThemeConfig.configList.toList() } val themeList = remember(listVersion) { OldThemeConfig.configList.toList() }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -36,7 +36,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.DictRule import io.legado.app.data.entities.DictRule
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -56,7 +56,8 @@ fun DictRuleEditSheet(
var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") } var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") }
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
GlassModalBottomSheet( AppModalBottomSheet(
show = true,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Box( Box(
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.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
@@ -44,13 +43,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseRuleEvent import io.legado.app.base.BaseRuleEvent
import io.legado.app.data.entities.DictRule import io.legado.app.data.entities.DictRule
import io.legado.app.ui.theme.LegadoTheme
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.button.SmallIconButton import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -153,8 +150,8 @@ fun DictRuleScreen(
) )
} }
if (showExportSheet) {
FilePickerSheet( FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export), title = stringResource(R.string.export),
onSelectSysDir = { onSelectSysDir = {
@@ -167,10 +164,10 @@ fun DictRuleScreen(
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
}
if (showImportSheet) {
FilePickerSheet( FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false }, onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_dict_rule), title = stringResource(R.string.import_dict_rule),
onSelectSysFile = { types -> onSelectSysFile = { types ->
@@ -183,24 +180,19 @@ fun DictRuleScreen(
}, },
allowExtensions = arrayOf("json", "txt") allowExtensions = arrayOf("json", "txt")
) )
}
(importState as? BaseImportUiState.Success<DictRule>)?.let { state ->
BatchImportDialog( BatchImportDialog(
title = "导入词典规则", title = "导入词典规则",
importState = state, importState = importState,
onDismissRequest = { viewModel.cancelImport() }, onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) }, onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) }, onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() }, onConfirm = { viewModel.saveImportedRules() },
itemContent = { rule, _ -> itemTitle = { rule -> rule.name },
Column { itemSubtitle = { rule ->
AppText(rule.name, style = LegadoTheme.typography.titleMedium) rule.urlRule.takeIf { it.isNotBlank() }
AppText(rule.urlRule, style = LegadoTheme.typography.bodySmall, maxLines = 1)
}
} }
) )
}
LaunchedEffect(reorderableState.isAnyItemDragging) { LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) {
@@ -228,8 +220,8 @@ fun DictRuleScreen(
) )
} }
if (showEditSheet) {
RuleEditSheet( RuleEditSheet(
show = showEditSheet,
rule = editingRule, rule = editingRule,
title = stringResource(R.string.dict_rule), title = stringResource(R.string.dict_rule),
label1 = stringResource(R.string.url_rule), label1 = stringResource(R.string.url_rule),
@@ -268,7 +260,6 @@ fun DictRuleScreen(
) )
} }
) )
}
RuleListScaffold( RuleListScaffold(
title = "字典规则", title = "字典规则",
@@ -310,7 +301,7 @@ fun DictRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_str)) }, text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
@@ -26,6 +26,7 @@ import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.WideNavigationRail import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem import androidx.compose.material3.WideNavigationRailItem
import androidx.compose.material3.WideNavigationRailValue import androidx.compose.material3.WideNavigationRailValue
@@ -195,7 +196,7 @@ fun MainScreen(
) { dismiss -> ) { dismiss ->
bookshelfUiState.groups.forEachIndexed { groupIndex, group -> bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(group.groupName) }, text = group.groupName,
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
if (pagerState.currentPage != index) { if (pagerState.currentPage != index) {
@@ -321,7 +322,7 @@ private fun NavigationIcon(
val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon
Box(modifier = modifier) { Box(modifier = modifier) {
if (destination == MainDestination.Bookshelf && upBooksCount > 0) { if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
BadgedBox(badge = { Badge { AppText(upBooksCount.toString()) } }) { BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) {
Icon(icon, contentDescription = null) Icon(icon, contentDescription = null)
} }
} else { } else {
@@ -31,38 +31,37 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookshelfConfigSheet( fun BookshelfConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(
title = stringResource(R.string.bookshelf_layout),
show = show,
onDismissRequest = onDismissRequest
) {
GlassCard() {
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.animateContentSize() .animateContentSize()
.padding(horizontal = 16.dp)
.padding(bottom = 32.dp), .padding(bottom = 32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
AppText(
text = stringResource(R.string.bookshelf_layout),
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp)
)
// Group Style
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = stringResource(R.string.group_style), title = stringResource(R.string.group_style),
selectedValue = BookshelfConfig.bookGroupStyle.toString(), selectedValue = BookshelfConfig.bookGroupStyle.toString(),
@@ -40,12 +40,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import io.legado.app.ui.widget.components.text.AppText
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.pullToRefresh import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -60,13 +57,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import io.legado.app.R import io.legado.app.R
@@ -86,7 +81,8 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
import io.legado.app.ui.widget.components.list.ListScaffold import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.tabRow.AdaptiveTabRow import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.readText import io.legado.app.utils.readText
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -237,37 +233,37 @@ fun BookshelfScreen(
topBarActions = { }, topBarActions = { },
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.add_remote_book)) }, text = stringResource(R.string.add_remote_book),
onClick = { context.startActivity<RemoteBookActivity>(); dismiss() }, onClick = { context.startActivity<RemoteBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Wifi, null) } leadingIcon = { Icon(Icons.Default.Wifi, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.book_local)) }, text = stringResource(R.string.book_local),
onClick = { context.startActivity<ImportBookActivity>(); dismiss() }, onClick = { context.startActivity<ImportBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Save, null) } leadingIcon = { Icon(Icons.Default.Save, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.update_toc)) }, text = stringResource(R.string.update_toc),
onClick = { viewModel.upToc(uiState.items); dismiss() }, onClick = { viewModel.upToc(uiState.items); dismiss() },
leadingIcon = { Icon(Icons.Default.Refresh, null) } leadingIcon = { Icon(Icons.Default.Refresh, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("布局设置") }, text = "布局设置",
onClick = { showConfigSheet = true; dismiss() }, onClick = { showConfigSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.GridView, null) } leadingIcon = { Icon(Icons.Default.GridView, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.group_manage)) }, text = stringResource(R.string.group_manage),
onClick = { showGroupManageSheet = true; dismiss() }, onClick = { showGroupManageSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Edit, null) } leadingIcon = { Icon(Icons.Default.Edit, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.add_url)) }, text = stringResource(R.string.add_url),
onClick = { showAddUrlDialog = true; dismiss() }, onClick = { showAddUrlDialog = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Link, null) } leadingIcon = { Icon(Icons.Default.Link, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.bookshelf_management)) }, text = stringResource(R.string.bookshelf_management),
onClick = { onClick = {
val groupId = val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -279,7 +275,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Settings, null) } leadingIcon = { Icon(Icons.Default.Settings, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.cache_export)) }, text = stringResource(R.string.cache_export),
onClick = { onClick = {
val groupId = val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -291,7 +287,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Download, null) } leadingIcon = { Icon(Icons.Default.Download, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.export_bookshelf)) }, text = stringResource(R.string.export_bookshelf),
onClick = { onClick = {
showExportSheet = true showExportSheet = true
dismiss() dismiss()
@@ -299,12 +295,12 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.ImportExport, null) } leadingIcon = { Icon(Icons.Default.ImportExport, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_bookshelf)) }, text = stringResource(R.string.import_bookshelf),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.log)) }, text = stringResource(R.string.log),
onClick = { onClick = {
showLogSheet = true showLogSheet = true
dismiss() dismiss()
@@ -326,7 +322,7 @@ fun BookshelfScreen(
uiState.groups.map { it.groupName } uiState.groups.map { it.groupName }
} }
AdaptiveTabRow( AppTabRow(
tabTitles = tabTitles, tabTitles = tabTitles,
selectedTabIndex = selectedTabIndex, selectedTabIndex = selectedTabIndex,
onTabSelected = { index -> onTabSelected = { index ->
@@ -349,7 +345,7 @@ fun BookshelfScreen(
) { dismiss -> ) { dismiss ->
uiState.groups.forEachIndexed { index, group -> uiState.groups.forEachIndexed { index, group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(group.groupName) }, text = group.groupName,
onClick = { onClick = {
scope.launch { pagerState.animateScrollToPage(index) } scope.launch { pagerState.animateScrollToPage(index) }
dismiss() dismiss()
@@ -487,13 +483,15 @@ fun BookshelfScreen(
} }
} }
if (showConfigSheet) { BookshelfConfigSheet(
BookshelfConfigSheet(onDismissRequest = { showConfigSheet = false }) show = showConfigSheet,
} onDismissRequest = { showConfigSheet = false }
)
if (showGroupManageSheet) { GroupManageSheet(
GroupManageSheet(onDismissRequest = { showGroupManageSheet = false }) show = showGroupManageSheet,
} onDismissRequest = { showGroupManageSheet = false }
)
if (showAddUrlDialog) { if (showAddUrlDialog) {
SourceInputDialog( SourceInputDialog(
@@ -506,8 +504,8 @@ fun BookshelfScreen(
) )
} }
if (showImportSheet) {
FilePickerSheet( FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false }, onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_bookshelf), title = stringResource(R.string.import_bookshelf),
onSelectSysFile = { types -> onSelectSysFile = { types ->
@@ -520,10 +518,9 @@ fun BookshelfScreen(
}, },
allowExtensions = arrayOf("json", "txt") allowExtensions = arrayOf("json", "txt")
) )
}
if (showExportSheet) {
FilePickerSheet( FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export_bookshelf), title = stringResource(R.string.export_bookshelf),
onSelectSysDir = { onSelectSysDir = {
@@ -535,11 +532,11 @@ fun BookshelfScreen(
viewModel.uploadBookshelf(uiState.items) viewModel.uploadBookshelf(uiState.items)
} }
) )
}
if (showLogSheet) { AppLogSheet(
AppLogSheet(onDismissRequest = { showLogSheet = false }) show = showLogSheet,
} onDismissRequest = { showLogSheet = false }
)
if (uiState.isLoading) { if (uiState.isLoading) {
Dialog(onDismissRequest = {}) { Dialog(onDismissRequest = {}) {
@@ -6,18 +6,13 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -26,7 +21,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -35,10 +29,9 @@ import io.legado.app.R
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.book.group.GroupEditContent import io.legado.app.ui.book.group.GroupEditContent
import io.legado.app.ui.book.group.GroupViewModel import io.legado.app.ui.book.group.GroupViewModel
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.move import io.legado.app.utils.move
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -46,6 +39,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupManageSheet( fun GroupManageSheet(
show: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: GroupViewModel = koinViewModel(), viewModel: GroupViewModel = koinViewModel(),
bookshelfViewModel: BookshelfViewModel = koinViewModel() bookshelfViewModel: BookshelfViewModel = koinViewModel()
@@ -78,7 +72,22 @@ fun GroupManageSheet(
} }
} }
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest,
title = if (!isEditing) stringResource(R.string.group_manage) else stringResource(R.string.group_edit),
endAction = {
if (!isEditing) {
SmallIconButton(
onClick = {
editingGroup = null
isEditing = true
},
icon = Icons.Default.Add
)
}
}
) {
AnimatedContent( AnimatedContent(
targetState = isEditing, targetState = isEditing,
transitionSpec = { transitionSpec = {
@@ -93,33 +102,6 @@ fun GroupManageSheet(
viewModel = viewModel viewModel = viewModel
) )
} else { } else {
Column(
modifier = Modifier
.fillMaxWidth()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
AppText(
text = stringResource(R.string.group_manage),
style = LegadoTheme.typography.titleLarge
)
IconButton(onClick = {
editingGroup = null
isEditing = true
}) {
Icon(
Icons.Default.Add,
contentDescription = stringResource(R.string.add)
)
}
}
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -148,4 +130,3 @@ fun GroupManageSheet(
} }
} }
} }
}
@@ -153,13 +153,13 @@ fun ExploreScreen(
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) }, leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { AppText(stringResource(R.string.all)) }, text = stringResource(R.string.all),
onClick = { viewModel.setGroup(""); dismiss() } onClick = { viewModel.setGroup(""); dismiss() }
) )
uiState.groups.forEach { group -> uiState.groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { AppText(group) }, text = group,
onClick = { viewModel.setGroup(group); dismiss() } onClick = { viewModel.setGroup(group); dismiss() }
) )
} }
@@ -416,29 +416,29 @@ fun ExploreSourceHeader(
PillHeaderDivider(title = item.bookSourceName) PillHeaderDivider(title = item.bookSourceName)
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) }, leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { AppText(stringResource(R.string.to_top)) }, text = stringResource(R.string.to_top),
onClick = { onTop(); showMenu = false } onClick = { onTop(); showMenu = false }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) }, leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { AppText(stringResource(R.string.edit)) }, text = stringResource(R.string.edit),
onClick = { onEdit(); showMenu = false } onClick = { onEdit(); showMenu = false }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Search) }, leadingIcon = { MenuItemIcon(Icons.Default.Search) },
text = { AppText(stringResource(R.string.search)) }, text = stringResource(R.string.search),
onClick = { onSearch(); showMenu = false } onClick = { onSearch(); showMenu = false }
) )
if (item.hasLoginUrl) { if (item.hasLoginUrl) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { AppText(stringResource(R.string.login)) }, text = stringResource(R.string.login),
onClick = { onLogin(); showMenu = false } onClick = { onLogin(); showMenu = false }
) )
} }
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Refresh) }, leadingIcon = { MenuItemIcon(Icons.Default.Refresh) },
text = { AppText(stringResource(R.string.refresh)) }, text = stringResource(R.string.refresh),
onClick = { onRefresh(); showMenu = false } onClick = { onRefresh(); showMenu = false }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
@@ -448,12 +448,8 @@ fun ExploreSourceHeader(
tint = MaterialTheme.colorScheme.error tint = MaterialTheme.colorScheme.error
) )
}, },
text = { text = stringResource(R.string.delete),
AppText( color = LegadoTheme.colorScheme.error,
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
onClick = { onDelete(); showMenu = false } onClick = { onDelete(); showMenu = false }
) )
} }
@@ -239,7 +239,7 @@ fun WebServiceSettingBlock(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp), .padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
SmallTextButton( SmallTextButton(
@@ -136,12 +136,12 @@ fun RssScreen(
dismiss() dismiss()
}, },
leadingIcon = { MenuItemIcon(Icons.Default.Settings) }, leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
text = { AppText("订阅源管理") } text = "订阅源管理"
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) }, leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { AppText(stringResource(R.string.all)) }, text = stringResource(R.string.all),
onClick = { onClick = {
viewModel.setGroup("") viewModel.setGroup("")
dismiss() dismiss()
@@ -150,7 +150,7 @@ fun RssScreen(
uiState.groups.forEach { group -> uiState.groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { AppText(group) }, text = group,
onClick = { onClick = {
viewModel.setGroup(group) viewModel.setGroup(group)
dismiss() dismiss()
@@ -247,7 +247,7 @@ fun RssSourceGridItem(
PillHeaderDivider(title = source.sourceName) PillHeaderDivider(title = source.sourceName)
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) }, leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { AppText(stringResource(R.string.to_top)) }, text = stringResource(R.string.to_top),
onClick = { onClick = {
onTop() onTop()
showMenu = false showMenu = false
@@ -255,7 +255,7 @@ fun RssSourceGridItem(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) }, leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { AppText(stringResource(R.string.edit)) }, text = stringResource(R.string.edit),
onClick = { onClick = {
onEdit() onEdit()
showMenu = false showMenu = false
@@ -264,7 +264,7 @@ fun RssSourceGridItem(
if (!source.loginUrl.isNullOrBlank()) { if (!source.loginUrl.isNullOrBlank()) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { AppText(stringResource(R.string.login)) }, text = stringResource(R.string.login),
onClick = { onClick = {
onLogin() onLogin()
showMenu = false showMenu = false
@@ -273,7 +273,7 @@ fun RssSourceGridItem(
} }
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Close) }, leadingIcon = { MenuItemIcon(Icons.Default.Close) },
text = { AppText(stringResource(R.string.disable_source)) }, text = stringResource(R.string.disable_source),
onClick = { onClick = {
onDisable() onDisable()
showMenu = false showMenu = false
@@ -286,12 +286,8 @@ fun RssSourceGridItem(
tint = MaterialTheme.colorScheme.error tint = MaterialTheme.colorScheme.error
) )
}, },
text = { text = stringResource(R.string.delete),
AppText( color = MaterialTheme.colorScheme.error,
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
onClick = { onClick = {
onDelete() onDelete()
showMenu = false showMenu = false
@@ -8,7 +8,6 @@ import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.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
@@ -19,7 +18,6 @@ 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.FileOpen import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
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
@@ -57,7 +55,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseRuleEvent 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.theme.LegadoTheme
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.GroupManageBottomSheet import io.legado.app.ui.widget.components.GroupManageBottomSheet
@@ -70,7 +67,7 @@ import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.tabRow.AdaptiveTabRow import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -161,8 +158,8 @@ fun ReplaceRuleScreen(
) )
} }
if (showImportSheet) {
FilePickerSheet( FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false }, onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_replace_rule), title = stringResource(R.string.import_replace_rule),
onSelectSysFile = { types -> onSelectSysFile = { types ->
@@ -175,10 +172,9 @@ fun ReplaceRuleScreen(
}, },
allowExtensions = arrayOf("json", "txt") allowExtensions = arrayOf("json", "txt")
) )
}
if (showExportSheet) {
FilePickerSheet( FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
onSelectSysDir = { onSelectSysDir = {
showExportSheet = false showExportSheet = false
@@ -190,27 +186,20 @@ fun ReplaceRuleScreen(
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
}
(importState as? BaseImportUiState.Success<ReplaceRule>)?.let { state ->
BatchImportDialog( BatchImportDialog(
title = stringResource(R.string.import_replace_rule), title = stringResource(R.string.import_replace_rule),
importState = state, importState = importState,
onDismissRequest = { viewModel.cancelImport() }, onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) }, onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) }, onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() }, onConfirm = { viewModel.saveImportedRules() },
topBarActions = {}, topBarActions = {},
itemContent = { rule, _ -> itemTitle = { rule -> rule.name },
Column { itemSubtitle = { rule ->
AppText(rule.name, style = LegadoTheme.typography.titleMedium) rule.group?.takeIf { it.isNotBlank() }
if (!rule.group.isNullOrBlank()) {
AppText(rule.group!!, style = LegadoTheme.typography.bodySmall)
}
}
} }
) )
}
if (importState is BaseImportUiState.Loading) { if (importState is BaseImportUiState.Loading) {
Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() } Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() }
@@ -263,14 +252,15 @@ fun ReplaceRuleScreen(
} }
} }
if (showGroupManageSheet) {
GroupManageBottomSheet( GroupManageBottomSheet(
show = showGroupManageSheet,
groups = groups, groups = groups,
onDismissRequest = { showGroupManageSheet = false }, onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) }, onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) } onDeleteGroup = { viewModel.delGroup(it) }
) )
}
showDeleteRuleDialog?.let { rule -> showDeleteRuleDialog?.let { rule ->
AlertDialog( AlertDialog(
@@ -343,7 +333,7 @@ fun ReplaceRuleScreen(
}, },
bottomContent = { bottomContent = {
if (tabItems.size > 1) { if (tabItems.size > 1) {
AdaptiveTabRow( AppTabRow(
tabTitles = tabItems, tabTitles = tabItems,
selectedTabIndex = selectedTabIndex, selectedTabIndex = selectedTabIndex,
onTabSelected = { index -> onTabSelected = { index ->
@@ -384,29 +374,29 @@ fun ReplaceRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_str)) }, text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("分组管理") }, text = "分组管理",
onClick = { showGroupManageSheet = true; dismiss() } onClick = { showGroupManageSheet = true; dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("帮助") }, text = "帮助",
onClick = { /*TODO*/ dismiss() } onClick = { /*TODO*/ dismiss() }
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("旧的在前") }, text = "旧的在前",
onClick = { viewModel.setSortMode("asc"); dismiss() } onClick = { viewModel.setSortMode("asc"); dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("新的在前") }, text = "新的在前",
onClick = { viewModel.setSortMode("desc"); dismiss() } onClick = { viewModel.setSortMode("desc"); dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("名称升序") }, text = "名称升序",
onClick = { onClick = {
viewModel.setSortMode("name_asc") viewModel.setSortMode("name_asc")
dismiss() dismiss()
@@ -416,7 +406,7 @@ fun ReplaceRuleScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("名称降序") }, text = "名称降序",
onClick = { onClick = {
viewModel.setSortMode("name_desc") viewModel.setSortMode("name_desc")
dismiss() dismiss()
@@ -473,16 +463,16 @@ fun ReplaceRuleScreen(
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp)) //clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
), ),
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
DropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("移至顶部") }, text = "移至顶部",
onClick = { viewModel.toTop(ui.rule); dismiss() } onClick = { viewModel.toTop(ui.rule); dismiss() }
) )
DropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("移至底部") }, text = "移至底部",
onClick = { viewModel.toBottom(ui.rule); dismiss() } onClick = { viewModel.toBottom(ui.rule); dismiss() }
) )
DropdownMenuItem( RoundDropdownMenuItem(
text = { AppText("删除") }, text = "删除",
onClick = { showDeleteRuleDialog = ui.rule; dismiss() } onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
) )
} }
@@ -3,11 +3,9 @@ package io.legado.app.ui.rss.favorites
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -44,7 +42,6 @@ import io.legado.app.ui.widget.components.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -129,11 +126,10 @@ fun RssFavoritesScreen(
topBarActions = {}, topBarActions = {},
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.all),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp)) Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
AppText(stringResource(R.string.all))
} }
}, },
onClick = { onClick = {
@@ -143,15 +139,14 @@ fun RssFavoritesScreen(
) )
groups.forEach { group -> groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = group,
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon( Icon(
Icons.AutoMirrored.Outlined.Label, Icons.AutoMirrored.Outlined.Label,
null, null,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
) )
Spacer(Modifier.width(12.dp))
AppText(group)
} }
}, },
onClick = { onClick = {
@@ -162,11 +157,10 @@ fun RssFavoritesScreen(
} }
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.delete_select_group),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp)) Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
AppText(stringResource(R.string.delete_select_group))
} }
}, },
onClick = { onClick = {
@@ -175,11 +169,10 @@ fun RssFavoritesScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.all),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp)) Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
AppText(stringResource(R.string.all))
} }
}, },
onClick = { onClick = {
@@ -248,14 +241,14 @@ fun RssFavoritesScreen(
}, },
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.change_group)) }, text = stringResource(R.string.change_group),
onClick = { onClick = {
showSetGroupDialog = rssStar showSetGroupDialog = rssStar
dismiss() dismiss()
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.delete)) }, text = stringResource(R.string.delete),
onClick = { onClick = {
viewModel.deleteStar(rssStar) viewModel.deleteStar(rssStar)
dismiss() dismiss()
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.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
@@ -41,7 +40,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseRuleEvent import io.legado.app.base.BaseRuleEvent
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.ui.theme.LegadoTheme
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.GroupManageBottomSheet import io.legado.app.ui.widget.components.GroupManageBottomSheet
@@ -50,7 +48,6 @@ import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.dialog.TextListInputDialog import io.legado.app.ui.widget.components.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -175,17 +172,17 @@ fun RssSourceScreen(
) )
} }
if (showGroupManageSheet) {
GroupManageBottomSheet( GroupManageBottomSheet(
show = showGroupManageSheet,
groups = groups, groups = groups,
onDismissRequest = { showGroupManageSheet = false }, onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) }, onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) } onDeleteGroup = { viewModel.delGroup(it) }
) )
}
if (showFilePickerSheet) {
FilePickerSheet( FilePickerSheet(
show = showFilePickerSheet,
onDismissRequest = { showFilePickerSheet = false }, onDismissRequest = { showFilePickerSheet = false },
onSelectSysDir = { onSelectSysDir = {
showFilePickerSheet = false showFilePickerSheet = false
@@ -197,28 +194,19 @@ fun RssSourceScreen(
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
}
(importState as? BaseImportUiState.Success<RssSource>)?.let { state ->
BatchImportDialog( BatchImportDialog(
title = stringResource(R.string.import_rss_source), title = stringResource(R.string.import_rss_source),
importState = state, importState = importState,
onDismissRequest = { viewModel.cancelImport() }, onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) }, onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) }, onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() }, onConfirm = { viewModel.saveImportedRules() },
itemContent = { source, _ -> itemTitle = { rule -> rule.sourceName },
Column { itemSubtitle = { rule ->
AppText(source.sourceName, style = LegadoTheme.typography.titleMedium) rule.sourceUrl.takeIf { it.isNotBlank() }
AppText(
source.sourceUrl,
style = LegadoTheme.typography.bodySmall,
maxLines = 1
)
}
} }
) )
}
LaunchedEffect(reorderableState.isAnyItemDragging) { LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) {
@@ -291,11 +279,11 @@ fun RssSourceScreen(
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
onClick = { showGroupManageSheet = true }, onClick = { showGroupManageSheet = true },
text = { AppText("分组管理") }, text = "分组管理",
) )
Box { Box {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_rss_source)) }, text = stringResource(R.string.import_rss_source),
onClick = { showImportMenu = true } onClick = { showImportMenu = true }
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -303,7 +291,7 @@ fun RssSourceScreen(
onDismissRequest = { showImportMenu = false } onDismissRequest = { showImportMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_on_line)) }, text = stringResource(R.string.import_on_line),
onClick = { onClick = {
showImportMenu = false showImportMenu = false
dismiss() dismiss()
@@ -311,7 +299,7 @@ fun RssSourceScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_local)) }, text = stringResource(R.string.import_local),
onClick = { onClick = {
showImportMenu = false showImportMenu = false
dismiss() dismiss()
@@ -319,7 +307,7 @@ fun RssSourceScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_default_rule)) }, text = stringResource(R.string.import_default_rule),
onClick = { onClick = {
showImportMenu = false showImportMenu = false
dismiss() dismiss()
@@ -330,29 +318,29 @@ fun RssSourceScreen(
} }
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.all)) }, text = stringResource(R.string.all),
onClick = { dismiss(); viewModel.setGroupFilter(null) } onClick = { dismiss(); viewModel.setGroupFilter(null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.enabled)) }, text = stringResource(R.string.enabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.disabled)) }, text = stringResource(R.string.disabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.need_login)) }, text = stringResource(R.string.need_login),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.no_group)) }, text = stringResource(R.string.no_group),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) }
) )
PillDivider() PillDivider()
groups.forEach { group -> groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(group) }, text = group,
onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$group") } onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$group") }
) )
} }
@@ -89,7 +89,8 @@ fun RuleSubScreen(
topBarActions = {}, topBarActions = {},
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.sort),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp)) Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.size(12.dp)) Spacer(Modifier.size(12.dp))
@@ -185,7 +186,7 @@ fun RuleSubScreen(
}, },
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.delete)) }, text = stringResource(R.string.delete),
onClick = { onClick = {
viewModel.delete(ruleSub) viewModel.delete(ruleSub)
dismiss() dismiss()
@@ -11,6 +11,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.theme.ThemeController import top.yukonga.miuix.kmp.theme.ThemeController
@@ -46,8 +47,15 @@ fun AppTheme(
) )
} }
val themeColors = remember(colorScheme, darkTheme, seedColor, paletteStyle, colorSchemeMode) { val themeColors = remember(
LegadoThemeColors( colorScheme,
darkTheme,
seedColor,
paletteStyle,
colorSchemeMode,
composeEngine
) {
LegadoThemeMode(
colorScheme = colorScheme, colorScheme = colorScheme,
isDark = darkTheme, isDark = darkTheme,
seedColor = seedColor, seedColor = seedColor,
@@ -61,43 +69,228 @@ fun AppTheme(
CompositionLocalProvider( CompositionLocalProvider(
LocalLegadoThemeColors provides themeColors LocalLegadoThemeColors provides themeColors
) { ) {
when { if (ThemeResolver.isMiuixEngine(themeColors.composeEngine)) {
ThemeResolver.isMiuixEngine(themeColors.composeEngine) -> {
val controller = remember(colorSchemeMode, darkTheme) { val controller = remember(colorSchemeMode, darkTheme) {
ThemeController( ThemeController(colorSchemeMode = colorSchemeMode, isDark = darkTheme)
colorSchemeMode = colorSchemeMode, }
isDark = darkTheme
MiuixTheme(controller = controller) {
val miuixStyles = MiuixTheme.textStyles
val legadoTypography = remember(miuixStyles) {
LegadoTypography(
headlineLarge = miuixStyles.title1,
headlineLargeEmphasized = miuixStyles.title1.copy(fontWeight = FontWeight.Medium),
headlineMedium = miuixStyles.title2,
headlineMediumEmphasized = miuixStyles.title2.copy(fontWeight = FontWeight.Medium),
headlineSmall = miuixStyles.title3,
headlineSmallEmphasized = miuixStyles.title3.copy(fontWeight = FontWeight.Medium),
titleLarge = miuixStyles.headline1,
titleLargeEmphasized = miuixStyles.headline1.copy(fontWeight = FontWeight.Medium),
titleMedium = miuixStyles.headline2,
titleMediumEmphasized = miuixStyles.headline2.copy(fontWeight = FontWeight.Medium),
titleSmall = miuixStyles.subtitle,
titleSmallEmphasized = miuixStyles.subtitle.copy(fontWeight = FontWeight.Medium),
bodyLarge = miuixStyles.paragraph,
bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
bodyMedium = miuixStyles.body1,
bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
bodySmall = miuixStyles.body2,
bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium),
labelLarge = miuixStyles.button,
labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium),
labelMedium = miuixStyles.footnote1,
labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
labelSmall = miuixStyles.footnote2,
labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
) )
} }
MiuixTheme( val miuixColorScheme = MiuixTheme.colorScheme
controller = controller
) { val mappedColorScheme = remember(miuixColorScheme) {
val miuixTextStyles = MiuixTheme.textStyles LegadoColorScheme(
val mappedTypography = remember(miuixTextStyles) { primary = miuixColorScheme.primary,
miuixStylesToM3Typography(miuixTextStyles) onPrimary = miuixColorScheme.onPrimary,
primaryContainer = miuixColorScheme.primaryContainer,
onPrimaryContainer = miuixColorScheme.onPrimaryContainer,
inversePrimary = miuixColorScheme.primaryVariant,
secondary = miuixColorScheme.primary.copy(alpha = 0.5f),
onSecondary = miuixColorScheme.onPrimary.copy(alpha = 0.5f),
secondaryContainer = miuixColorScheme.primaryContainer.copy(alpha = 0.5f),
onSecondaryContainer = miuixColorScheme.onPrimaryContainer.copy(alpha = 0.5f),
tertiary = miuixColorScheme.secondary,
onTertiary = miuixColorScheme.onSecondary,
tertiaryContainer = miuixColorScheme.secondaryContainer,
onTertiaryContainer = miuixColorScheme.onSecondaryContainer,
// ================= 4. 背景与表面 (Background & Surface) =================
background = miuixColorScheme.background,
onBackground = miuixColorScheme.onBackground,
surface = miuixColorScheme.surface,
onSurface = miuixColorScheme.onSurface,
surfaceVariant = miuixColorScheme.surfaceVariant,
// M3 的 onSurfaceVariant 通常是次级文字色。Miuix 的 onSurfaceSecondary 完美契合这个语义
onSurfaceVariant = miuixColorScheme.onSurfaceSecondary,
// M3 中用于给 Surface 叠加一层极淡主题色的属性,通常直接取 primary
surfaceTint = miuixColorScheme.primary,
// Inverse 系列通常用于深色模式下的反色提示(如 Snackbar)。
// 简单映射法:直接用现有的 onSurface 和 surface 交叉互换。
inverseSurface = miuixColorScheme.onSurface,
inverseOnSurface = miuixColorScheme.surface,
// ================= 5. 错误状态 (Error) =================
error = miuixColorScheme.error,
onError = miuixColorScheme.onError,
errorContainer = miuixColorScheme.errorContainer,
onErrorContainer = miuixColorScheme.onErrorContainer,
// ================= 6. 边框、分割线与遮罩 (Outline & Scrim) =================
outline = miuixColorScheme.outline,
// outlineVariant 在 M3 中常用于分割线。Miuix 刚好有 dividerLine
outlineVariant = miuixColorScheme.dividerLine,
// scrim 是 M3 的遮罩层(如弹窗背后的阴影)。Miuix 刚好有 windowDimming
scrim = miuixColorScheme.windowDimming,
surfaceBright = miuixColorScheme.surface, // Miuix 缺省,用 surface 兜底
surfaceDim = miuixColorScheme.background, // Miuix 缺省,用 background 兜底
surfaceContainer = miuixColorScheme.surfaceContainer,
surfaceContainerHigh = miuixColorScheme.surfaceContainerHigh,
surfaceContainerHighest = miuixColorScheme.surfaceContainerHighest,
surfaceContainerLow = miuixColorScheme.secondaryContainer,
surfaceContainerLowest = miuixColorScheme.background,
primaryFixed = miuixColorScheme.primaryContainer,
primaryFixedDim = miuixColorScheme.primary,
onPrimaryFixed = miuixColorScheme.onPrimaryContainer,
onPrimaryFixedVariant = miuixColorScheme.onPrimary,
secondaryFixed = miuixColorScheme.secondaryContainer,
secondaryFixedDim = miuixColorScheme.secondary,
onSecondaryFixed = miuixColorScheme.onSecondaryContainer,
onSecondaryFixedVariant = miuixColorScheme.onSecondary,
tertiaryFixed = miuixColorScheme.tertiaryContainer,
tertiaryFixedDim = miuixColorScheme.tertiaryContainerVariant,
onTertiaryFixed = miuixColorScheme.onTertiaryContainer,
onTertiaryFixedVariant = miuixColorScheme.onTertiaryContainer
)
} }
CompositionLocalProvider( CompositionLocalProvider(
LocalLegadoTypography provides mappedTypography LocalLegadoTypography provides legadoTypography,
LocalLegadoColorScheme provides mappedColorScheme
) { ) {
AppBackground(darkTheme = darkTheme) { AppBackground(darkTheme = darkTheme) { content() }
content()
} }
} }
} } else {
} val Typography = Typography()
else -> {
MaterialExpressiveTheme( MaterialExpressiveTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
typography = Typography(), typography = Typography,
motionScheme = MotionScheme.expressive(), motionScheme = MotionScheme.expressive(),
shapes = Shapes() shapes = Shapes()
) { ) {
AppBackground(darkTheme = darkTheme) { val legadoTypography = remember(Typography) {
content() LegadoTypography(
headlineLarge = Typography.headlineLarge,
headlineLargeEmphasized = Typography.headlineLargeEmphasized,
headlineMedium = Typography.headlineMedium,
headlineMediumEmphasized = Typography.headlineMediumEmphasized,
headlineSmall = Typography.headlineSmall,
headlineSmallEmphasized = Typography.headlineSmallEmphasized,
titleLarge = Typography.titleLarge,
titleLargeEmphasized = Typography.titleLargeEmphasized,
titleMedium = Typography.titleMedium,
titleMediumEmphasized = Typography.titleMediumEmphasized,
titleSmall = Typography.titleSmall,
titleSmallEmphasized = Typography.titleSmallEmphasized,
bodyLarge = Typography.bodyLarge,
bodyLargeEmphasized = Typography.bodyLargeEmphasized,
bodyMedium = Typography.bodyMedium,
bodyMediumEmphasized = Typography.bodyMediumEmphasized,
bodySmall = Typography.bodySmall,
bodySmallEmphasized = Typography.bodySmallEmphasized,
labelLarge = Typography.labelLarge,
labelLargeEmphasized = Typography.labelLargeEmphasized,
labelMedium = Typography.labelMedium,
labelMediumEmphasized = Typography.labelMediumEmphasized,
labelSmall = Typography.labelSmall,
labelSmallEmphasized = Typography.labelSmallEmphasized
)
} }
val semanticColors = remember(colorScheme) {
LegadoColorScheme(
primary = colorScheme.primary,
onPrimary = colorScheme.onPrimary,
primaryContainer = colorScheme.primaryContainer,
onPrimaryContainer = colorScheme.onPrimaryContainer,
inversePrimary = colorScheme.inversePrimary,
secondary = colorScheme.secondary,
onSecondary = colorScheme.onSecondary,
secondaryContainer = colorScheme.secondaryContainer,
onSecondaryContainer = colorScheme.onSecondaryContainer,
tertiary = colorScheme.tertiary,
onTertiary = colorScheme.onTertiary,
tertiaryContainer = colorScheme.tertiaryContainer,
onTertiaryContainer = colorScheme.onTertiaryContainer,
background = colorScheme.background,
onBackground = colorScheme.onBackground,
surface = colorScheme.surface,
onSurface = colorScheme.onSurface,
surfaceVariant = colorScheme.surfaceVariant,
onSurfaceVariant = colorScheme.onSurfaceVariant,
surfaceTint = colorScheme.surfaceTint,
inverseSurface = colorScheme.inverseSurface,
inverseOnSurface = colorScheme.inverseOnSurface,
error = colorScheme.error,
onError = colorScheme.onError,
errorContainer = colorScheme.errorContainer,
onErrorContainer = colorScheme.onErrorContainer,
outline = colorScheme.outline,
outlineVariant = colorScheme.outlineVariant,
scrim = colorScheme.scrim,
surfaceBright = colorScheme.surfaceBright,
surfaceDim = colorScheme.surfaceDim,
surfaceContainer = colorScheme.surfaceContainer,
surfaceContainerHigh = colorScheme.surfaceContainerHigh,
surfaceContainerHighest = colorScheme.surfaceContainerHighest,
surfaceContainerLow = colorScheme.surfaceContainerLow,
surfaceContainerLowest = colorScheme.surfaceContainerLowest,
primaryFixed = colorScheme.primaryFixed,
primaryFixedDim = colorScheme.primaryFixedDim,
onPrimaryFixed = colorScheme.onPrimaryFixed,
onPrimaryFixedVariant = colorScheme.onPrimaryFixedVariant,
secondaryFixed = colorScheme.secondaryFixed,
secondaryFixedDim = colorScheme.secondaryFixedDim,
onSecondaryFixed = colorScheme.onSecondaryFixed,
onSecondaryFixedVariant = colorScheme.onSecondaryFixedVariant,
tertiaryFixed = colorScheme.tertiaryFixed,
tertiaryFixedDim = colorScheme.tertiaryFixedDim,
onTertiaryFixed = colorScheme.onTertiaryFixed,
onTertiaryFixedVariant = colorScheme.onTertiaryFixedVariant
)
}
CompositionLocalProvider(
LocalLegadoTypography provides legadoTypography,
LocalLegadoColorScheme provides semanticColors
) {
AppBackground(darkTheme = darkTheme) { content() }
} }
} }
} }
@@ -1,18 +1,18 @@
package io.legado.app.ui.theme package io.legado.app.ui.theme
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.Typography
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import com.materialkolor.PaletteStyle import com.materialkolor.PaletteStyle
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import top.yukonga.miuix.kmp.theme.ColorSchemeMode import top.yukonga.miuix.kmp.theme.ColorSchemeMode
data class LegadoThemeColors( data class LegadoThemeMode(
val colorScheme: ColorScheme, val colorScheme: ColorScheme,
val isDark: Boolean, val isDark: Boolean,
val seedColor: Color, val seedColor: Color,
@@ -22,30 +22,117 @@ data class LegadoThemeColors(
val composeEngine: String, val composeEngine: String,
) )
data class LegadoColorScheme(
val primary: Color,
val onPrimary: Color,
val primaryContainer: Color,
val onPrimaryContainer: Color,
val inversePrimary: Color,
val secondary: Color,
val onSecondary: Color,
val secondaryContainer: Color,
val onSecondaryContainer: Color,
val tertiary: Color,
val onTertiary: Color,
val tertiaryContainer: Color,
val onTertiaryContainer: Color,
val background: Color,
val onBackground: Color,
val surface: Color,
val onSurface: Color,
val surfaceVariant: Color,
val onSurfaceVariant: Color,
val surfaceTint: Color,
val inverseSurface: Color,
val inverseOnSurface: Color,
val error: Color,
val onError: Color,
val errorContainer: Color,
val onErrorContainer: Color,
val outline: Color,
val outlineVariant: Color,
val scrim: Color,
val surfaceBright: Color,
val surfaceDim: Color,
val surfaceContainer: Color,
val surfaceContainerHigh: Color,
val surfaceContainerHighest: Color,
val surfaceContainerLow: Color,
val surfaceContainerLowest: Color,
val primaryFixed: Color,
val primaryFixedDim: Color,
val onPrimaryFixed: Color,
val onPrimaryFixedVariant: Color,
val secondaryFixed: Color,
val secondaryFixedDim: Color,
val onSecondaryFixed: Color,
val onSecondaryFixedVariant: Color,
val tertiaryFixed: Color,
val tertiaryFixedDim: Color,
val onTertiaryFixed: Color,
val onTertiaryFixedVariant: Color,
)
data class LegadoTypography(
val headlineLarge: TextStyle,
val headlineLargeEmphasized: TextStyle,
val headlineMedium: TextStyle,
val headlineMediumEmphasized: TextStyle,
val headlineSmall: TextStyle,
val headlineSmallEmphasized: TextStyle,
val titleLarge: TextStyle,
val titleLargeEmphasized: TextStyle,
val titleMedium: TextStyle,
val titleMediumEmphasized: TextStyle,
val titleSmall: TextStyle,
val titleSmallEmphasized: TextStyle,
val bodyLarge: TextStyle,
val bodyLargeEmphasized: TextStyle,
val bodyMedium: TextStyle,
val bodyMediumEmphasized: TextStyle,
val bodySmall: TextStyle,
val bodySmallEmphasized: TextStyle,
val labelLarge: TextStyle,
val labelLargeEmphasized: TextStyle,
val labelMedium: TextStyle,
val labelMediumEmphasized: TextStyle,
val labelSmall: TextStyle,
val labelSmallEmphasized: TextStyle,
)
val LocalLegadoColorScheme = staticCompositionLocalOf<LegadoColorScheme> {
error("No ColorScheme provided")
}
val LocalLegadoTypography = staticCompositionLocalOf<LegadoTypography> {
error("No Typography provided")
}
val LocalLegadoThemeColors = staticCompositionLocalOf { val LocalLegadoThemeColors = staticCompositionLocalOf {
LegadoThemeColors( LegadoThemeMode(
colorScheme = lightColorScheme(), colorScheme = lightColorScheme(),
isDark = false, isDark = false,
seedColor = Color.Unspecified, seedColor = Color.Unspecified,
paletteStyle = PaletteStyle.TonalSpot, paletteStyle = PaletteStyle.TonalSpot,
themeMode = ColorSchemeMode.System, themeMode = ColorSchemeMode.System,
useDynamicColor = true, useDynamicColor = true,
composeEngine = "m3" composeEngine = "material"
) )
} }
val LocalLegadoTypography = staticCompositionLocalOf {
Typography()
}
val LocalHazeState = compositionLocalOf<HazeState?> { null } val LocalHazeState = compositionLocalOf<HazeState?> { null }
object LegadoTheme { object LegadoTheme {
val colorScheme: ColorScheme val colorScheme: LegadoColorScheme
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalLegadoThemeColors.current.colorScheme get() = LocalLegadoColorScheme.current
val isDark: Boolean val isDark: Boolean
@Composable @Composable
@@ -77,7 +164,7 @@ object LegadoTheme {
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalLegadoThemeColors.current.useDynamicColor get() = LocalLegadoThemeColors.current.useDynamicColor
val typography: Typography val typography: LegadoTypography
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalLegadoTypography.current get() = LocalLegadoTypography.current
@@ -66,7 +66,8 @@ object ThemeManager {
} }
AppThemeMode.CUSTOM -> { AppThemeMode.CUSTOM -> {
CustomColorScheme(context.primaryColor, style) val colorSpec = ThemeResolver.resolveColorSpecFromMaterialVersion(materialVersion)
CustomColorScheme(context.primaryColor, style, colorSpec)
.getColorScheme(darkTheme) .getColorScheme(darkTheme)
} }
@@ -67,4 +67,12 @@ object ThemeResolver {
} }
} }
fun resolveColorSpecFromMaterialVersion(value: String?): ThemeColorSpec {
return when (value) {
"material3Expressive" -> ThemeColorSpec.SPEC_2025
"material3" -> ThemeColorSpec.SPEC_2021
else -> ThemeColorSpec.SPEC_2021
}
}
} }
@@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -22,26 +20,26 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun CollapsibleHeader( fun CollapsibleHeader(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showIcon: Boolean = true,
isCollapsed: Boolean, isCollapsed: Boolean,
onToggle: () -> Unit, onToggle: () -> Unit,
title: String, title: String,
subtitle: String? = null, subtitle: String? = null,
titleContent: (@Composable ColumnScope.() -> Unit)? = null titleContent: (@Composable ColumnScope.() -> Unit)? = null
) { ) {
Card( GlassCard(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 16.dp, top = 4.dp, end = 16.dp), .padding(start = 16.dp, top = 4.dp, end = 16.dp),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors( containerColor = LegadoTheme.colorScheme.surfaceContainer,
containerColor = MaterialTheme.colorScheme.surfaceContainer
),
onClick = onToggle onClick = onToggle
) { ) {
Row( Row(
@@ -59,7 +57,7 @@ fun CollapsibleHeader(
text = title, text = title,
style = LegadoTheme.typography.bodySmallEmphasized.copy( style = LegadoTheme.typography.bodySmallEmphasized.copy(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary color = LegadoTheme.colorScheme.primary
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -68,7 +66,7 @@ fun CollapsibleHeader(
AppText( AppText(
text = it, text = it,
style = LegadoTheme.typography.labelSmallEmphasized.copy( style = LegadoTheme.typography.labelSmallEmphasized.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -82,12 +80,14 @@ fun CollapsibleHeader(
label = "arrowRotation" label = "arrowRotation"
) )
if (showIcon) {
Icon( Icon(
imageVector = Icons.Default.ExpandMore, imageVector = Icons.Default.ExpandMore,
contentDescription = if (isCollapsed) "展开" else "折叠", contentDescription = if (isCollapsed) "展开" else "折叠",
modifier = Modifier.rotate(rotation), modifier = Modifier.rotate(rotation),
tint = MaterialTheme.colorScheme.primary tint = LegadoTheme.colorScheme.primary
) )
} }
} }
} }
}
@@ -1,49 +0,0 @@
package io.legado.app.ui.widget.components
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import androidx.compose.material3.LocalContentColor as MaterialLocalContentColor
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.theme.LocalContentColor as MiuixLocalContentColor
@Composable
fun AppIcon(
imageVector: ImageVector,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = currentContentColor(),
) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) {
MiuixIcon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier,
tint = tint
)
} else {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier,
tint = tint
)
}
}
@Composable
fun currentContentColor(): Color {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
return if (isMiuix) {
MiuixLocalContentColor.current
} else {
MaterialLocalContentColor.current
}
}
@@ -5,6 +5,7 @@ import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationBarItemDefaults
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.Color import androidx.compose.ui.graphics.Color
@@ -12,7 +13,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.NavigationBarDisplayMode import top.yukonga.miuix.kmp.basic.NavigationBarDisplayMode
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.NavigationBar as MiuixNavigationBar import top.yukonga.miuix.kmp.basic.NavigationBar as MiuixNavigationBar
@@ -85,7 +85,7 @@ fun RowScope.AppNavigationBarItem(
icon = m3Icon, icon = m3Icon,
colors = NavigationBarItemDefaults.colors(indicatorColor = m3IndicatorColor), colors = NavigationBarItemDefaults.colors(indicatorColor = m3IndicatorColor),
label = if (m3ShowLabel) { label = if (m3ShowLabel) {
{ AppText(labelString) } { Text(labelString) }
} else null, } else null,
alwaysShowLabel = m3AlwaysShowLabel alwaysShowLabel = m3AlwaysShowLabel
) )
@@ -0,0 +1,127 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.KeyboardActionHandler
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TextFieldLabelPosition
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Density
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.TextField as MiuixTextField
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppTextField(
state: TextFieldState,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
// 1. 核心思想:保留 AppText 的统一样式与颜色降级参数
textStyle: TextStyle? = null,
textColor: Color = Color.Unspecified,
// 2. 将 Label 统一定义为 String 以兼容两套引擎
label: String? = null,
// 3. 保留 M3 TextField 的所有其他参数
labelPosition: TextFieldLabelPosition = TextFieldLabelPosition.Attached(),
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
prefix: @Composable (() -> Unit)? = null,
suffix: @Composable (() -> Unit)? = null,
supportingText: @Composable (() -> Unit)? = null,
isError: Boolean = false,
inputTransformation: InputTransformation? = null,
outputTransformation: OutputTransformation? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
onKeyboardAction: KeyboardActionHandler? = null,
lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
scrollState: ScrollState = rememberScrollState(),
shape: Shape = TextFieldDefaults.shape,
colors: TextFieldColors = TextFieldDefaults.colors(),
contentPadding: PaddingValues? = null,
interactionSource: MutableInteractionSource? = null,
) {
val baseStyle = textStyle ?: LegadoTheme.typography.bodyMedium
val defaultTextColor = LegadoTheme.colorScheme.onSurface
val finalTextColor = textColor.takeOrElse {
baseStyle.color.takeOrElse { defaultTextColor }
}
val mergedTextStyle = baseStyle.merge(color = finalTextColor)
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
MiuixTextField(
state = state,
modifier = modifier,
enabled = enabled,
readOnly = readOnly,
textStyle = MiuixTheme.textStyles.main,
label = label ?: "",
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
inputTransformation = inputTransformation,
outputTransformation = outputTransformation,
keyboardOptions = keyboardOptions,
onKeyboardAction = onKeyboardAction,
lineLimits = lineLimits,
onTextLayout = onTextLayout,
scrollState = scrollState,
interactionSource = interactionSource
)
} else {
val resolvedContentPadding =
contentPadding ?: if (label == null || labelPosition is TextFieldLabelPosition.Above) {
TextFieldDefaults.contentPaddingWithoutLabel()
} else {
TextFieldDefaults.contentPaddingWithLabel()
}
TextField(
state = state,
modifier = modifier,
enabled = enabled,
readOnly = readOnly,
textStyle = mergedTextStyle,
labelPosition = labelPosition,
label = label?.let { { AppText(it) } },
placeholder = placeholder,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
prefix = prefix,
suffix = suffix,
supportingText = supportingText,
isError = isError,
inputTransformation = inputTransformation,
outputTransformation = outputTransformation,
keyboardOptions = keyboardOptions,
onKeyboardAction = onKeyboardAction,
lineLimits = lineLimits,
onTextLayout = onTextLayout,
scrollState = scrollState,
shape = shape,
colors = colors,
contentPadding = resolvedContentPadding,
interactionSource = interactionSource
)
}
}
@@ -24,6 +24,7 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -1,12 +1,10 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState
@@ -25,39 +23,29 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallTextButton import io.legado.app.ui.widget.components.button.SmallTextButton
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.SettingItem import io.legado.app.ui.widget.components.settingItem.SettingItem
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupManageBottomSheet( fun GroupManageBottomSheet(
show: Boolean,
groups: List<String>, groups: List<String>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit, onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit,
onDeleteGroup: (group: String) -> Unit onDeleteGroup: (group: String) -> Unit
) { ) {
GlassModalBottomSheet( AppModalBottomSheet(
onDismissRequest = onDismissRequest show = show,
onDismissRequest = onDismissRequest,
title = stringResource(R.string.group_manage),
) { ) {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppText(
modifier = Modifier.padding(bottom = 16.dp),
text = stringResource(R.string.group_manage),
style = LegadoTheme.typography.titleMedium
)
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(groups, key = { it }) { group -> items(groups, key = { it }) { group ->
GroupItem( GroupItem(
@@ -69,7 +57,6 @@ fun GroupManageBottomSheet(
} }
} }
} }
}
@Composable @Composable
private fun GroupItem( private fun GroupItem(
@@ -1,32 +1,14 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth 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.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.VerticalAlignTop
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -37,14 +19,15 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -53,10 +36,10 @@ fun SearchBarSection(
onQueryChange: (String) -> Unit, onQueryChange: (String) -> Unit,
placeholder: String = "搜索...", placeholder: String = "搜索...",
leadingIcon: @Composable (() -> Unit)? = { leadingIcon: @Composable (() -> Unit)? = {
SmallIconButton( AppIcon(
icon = Icons.Default.Search, imageVector = AppIcons.Search,
contentDescription = null, contentDescription = null
onClick = {}) )
}, },
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow, backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow,
scrollState: LazyListState? = null, scrollState: LazyListState? = null,
@@ -65,7 +48,6 @@ fun SearchBarSection(
dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null
) { ) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
val textFieldState = rememberTextFieldState(initialText = query) val textFieldState = rememberTextFieldState(initialText = query)
LaunchedEffect(query) { LaunchedEffect(query) {
@@ -91,79 +73,19 @@ fun SearchBarSection(
} }
} }
Surface( val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
modifier = Modifier
.fillMaxWidth() val searchTextField = @Composable {
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp), AppTextField(
shape = RoundedCornerShape(32.dp),
color = backgroundColor
) {
TextField(
state = textFieldState, state = textFieldState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = 48.dp) .then(
.padding(horizontal = 4.dp), if (isMiuix) Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
else Modifier
),
placeholder = { AppText(placeholder) }, placeholder = { AppText(placeholder) },
leadingIcon = leadingIcon, leadingIcon = leadingIcon,
trailingIcon = {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(end = 4.dp)
.animateContentSize()
) {
AnimatedVisibility(
visible = textFieldState.text.isNotEmpty(),
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
SmallIconButton(
onClick = { textFieldState.clearText() },
icon = Icons.Default.Clear,
contentDescription = "清空输入"
)
}
AnimatedVisibility(
visible = showScrollToTop,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
SmallIconButton(
onClick = {
scope.launch {
scrollState?.animateScrollToItem(0)
}
},
icon = Icons.Default.VerticalAlignTop,
contentDescription = "回到顶部"
)
}
if (trailingIcon != null) {
Box {
IconButton(
onClick = {
if (dropdownMenu != null) showMenu = true
}
) {
trailingIcon()
}
if (dropdownMenu != null) {
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
dropdownMenu { showMenu = false }
}
}
}
}
}
},
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent,
@@ -171,13 +93,21 @@ fun SearchBarSection(
disabledContainerColor = Color.Transparent, disabledContainerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
),
contentPadding = PaddingValues(
top = 4.dp,
bottom = 4.dp,
start = 12.dp,
end = 12.dp
) )
) )
} }
if (isMiuix) {
searchTextField()
} else {
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
shape = RoundedCornerShape(32.dp),
color = backgroundColor
) {
searchTextField()
}
}
} }
@@ -0,0 +1,52 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Remove
import androidx.compose.runtime.Composable
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.card.TextCard
@Composable
fun ValueStepper(
value: Float,
displayValue: Float,
valueRange: ClosedFloatingPointRange<Float>,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
SmallOutlinedIconButton(
onClick = {
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
onValueChange(newValue)
},
icon = Icons.Default.Remove
)
TextCard(
cornerRadius = 8.dp,
horizontalPadding = 8.dp,
verticalPadding = 4.dp,
text = displayValue.toInt().toString(),
backgroundColor = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onSurface
)
SmallOutlinedIconButton(
onClick = {
val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
onValueChange(newValue)
},
icon = Icons.Default.Add
)
}
}
@@ -7,13 +7,10 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -25,13 +22,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.widget.components.button.PrimaryButton
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.button.SecondaryButton
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookmarkEditSheet( fun BookmarkEditSheet(
show: Boolean,
bookmark: Bookmark, bookmark: Bookmark,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSave: (Bookmark) -> Unit, onSave: (Bookmark) -> Unit,
@@ -42,21 +41,16 @@ fun BookmarkEditSheet(
var bookText by remember { mutableStateOf(bookmark.bookText) } var bookText by remember { mutableStateOf(bookmark.bookText) }
var content by remember { mutableStateOf(bookmark.content) } var content by remember { mutableStateOf(bookmark.content) }
GlassModalBottomSheet( AppModalBottomSheet(
onDismissRequest = onDismiss show = show,
onDismissRequest = onDismiss,
title = bookmark.chapterName,
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp)
.navigationBarsPadding() .navigationBarsPadding()
) { ) {
AppText(
text = bookmark.chapterName,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField( OutlinedTextField(
value = bookText, value = bookText,
onValueChange = { bookText = it }, onValueChange = { bookText = it },
@@ -82,15 +76,13 @@ fun BookmarkEditSheet(
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
OutlinedButton( SecondaryButton(
onClick = { showDeleteConfirmDialog = true }, onClick = { showDeleteConfirmDialog = true },
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error), modifier = Modifier.weight(1f),
modifier = Modifier.weight(1f) text = "删除"
) { )
AppText("删除")
}
Button( PrimaryButton(
onClick = { onClick = {
val newBookmark = bookmark.apply { val newBookmark = bookmark.apply {
this.bookText = bookText this.bookText = bookText
@@ -98,10 +90,9 @@ fun BookmarkEditSheet(
} }
onSave(newBookmark) onSave(newBookmark)
}, },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f),
) { text = "保存"
AppText("保存") )
}
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -31,7 +30,7 @@ fun BookmarkItem(
onLongClick: () -> Unit onLongClick: () -> Unit
) { ) {
val backgroundColor by animateColorAsState( val backgroundColor by animateColorAsState(
targetValue = if (isDur) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f) targetValue = if (isDur) LegadoTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
else Color.Transparent, else Color.Transparent,
label = "BgColor" label = "BgColor"
) )
@@ -53,7 +52,7 @@ fun BookmarkItem(
AppText( AppText(
text = bookmark.chapterName, text = bookmark.chapterName,
style = LegadoTheme.typography.titleSmallEmphasized, style = LegadoTheme.typography.titleSmallEmphasized,
color = if (isDur) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.secondary
) )
if (bookmark.bookText.isNotEmpty()) { if (bookmark.bookText.isNotEmpty()) {
@@ -63,7 +62,7 @@ fun BookmarkItem(
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -72,7 +71,7 @@ fun BookmarkItem(
AppText( AppText(
text = bookmark.content, text = bookmark.content,
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary color = LegadoTheme.colorScheme.primary
) )
} }
} }
@@ -0,0 +1,74 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
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.Button as MiuixButton
import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
fun PrimaryButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: String
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = MiuixButtonDefaults.buttonColorsPrimary()
) {
MiuixText(
text = text
)
}
} else {
Button(
onClick = onClick,
modifier = modifier,
enabled = enabled,
) {
Text(
text = text,
)
}
}
}
@Composable
fun SecondaryButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: String
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = MiuixButtonDefaults.buttonColors()
) {
MiuixText(
text = text
)
}
} else {
OutlinedButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
) {
Text(
text = text
)
}
}
}
@@ -0,0 +1,61 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.IconButtonDefaults as MiuixIconButtonDefaults
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AppIconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = null,
//M3
colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
shape: Shape = IconButtonDefaults.standardShape,
// MIUIX
holdDownState: Boolean = false,
miuixBackgroundColor: Color = Color.Unspecified,
miuixCornerRadius: Dp? = null,
miuixMinHeight: Dp? = null,
miuixMinWidth: Dp? = null,
content: @Composable () -> Unit
) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
MiuixIconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
holdDownState = holdDownState,
backgroundColor = miuixBackgroundColor,
cornerRadius = MiuixIconButtonDefaults.CornerRadius,
minHeight = MiuixIconButtonDefaults.MinHeight,
minWidth = MiuixIconButtonDefaults.MinWidth,
content = content
)
} else {
IconButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = colors,
interactionSource = interactionSource,
shape = shape,
content = content
)
}
}
@@ -7,21 +7,21 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.legado.app.R import io.legado.app.R
import top.yukonga.miuix.kmp.basic.Icon import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.IconButton
import top.yukonga.miuix.kmp.icon.MiuixIcons import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.extended.Back import top.yukonga.miuix.kmp.icon.extended.Back
import top.yukonga.miuix.kmp.theme.LocalContentColor import top.yukonga.miuix.kmp.theme.LocalContentColor
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@Composable @Composable
fun MiuixButton( fun MiuixIconButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageVector: ImageVector = MiuixIcons.Regular.Back, imageVector: ImageVector = MiuixIcons.Regular.Back,
tint: Color = LocalContentColor.current, tint: Color = LocalContentColor.current,
contentDescription: String? = stringResource(id = R.string.back), contentDescription: String? = stringResource(id = R.string.back),
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
IconButton( MiuixIconButton(
onClick = onClick, onClick = onClick,
modifier = modifier modifier = modifier
) { ) {
@@ -44,7 +44,7 @@ fun MiuixToggleButton(
uncheckedTint: Color = LocalContentColor.current, uncheckedTint: Color = LocalContentColor.current,
contentDescription: String? = null, contentDescription: String? = null,
) { ) {
IconButton( MiuixIconButton(
onClick = { onCheckedChange(!checked) }, onClick = { onCheckedChange(!checked) },
modifier = modifier modifier = modifier
) { ) {
@@ -1,6 +1,8 @@
package io.legado.app.ui.widget.components.button package io.legado.app.ui.widget.components.button
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -80,8 +82,24 @@ fun SmallIconButton(
fun SmallOutlinedIconButton( fun SmallOutlinedIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, icon: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(32.dp),
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(18.dp)
)
}
} else {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
OutlinedIconButton( OutlinedIconButton(
onClick = onClick, onClick = onClick,
@@ -103,18 +121,35 @@ fun SmallOutlinedIconButton(
} }
} }
} }
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SmallTonalIconButton( fun SmallTonalIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, icon: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(32.dp),
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(18.dp)
)
}
} else {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
FilledTonalIconButton( FilledTonalIconButton(
onClick = onClick, onClick = onClick,
modifier = Modifier.size( modifier = modifier.size(
IconButtonDefaults.extraSmallContainerSize( IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform IconButtonDefaults.IconButtonWidthOption.Uniform
) )
@@ -125,13 +160,12 @@ fun SmallTonalIconButton(
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size( modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize)
IconButtonDefaults.extraSmallIconSize
)
) )
} }
} }
} }
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -142,6 +176,37 @@ fun SmallOutlinedIconToggleButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) {
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(32.dp),
backgroundColor = containerColor
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
tint = iconTint,
modifier = Modifier.size(18.dp)
)
}
} else {
val defaultShape = IconButtonDefaults.extraSmallRoundShape val defaultShape = IconButtonDefaults.extraSmallRoundShape
val pressedShape = IconButtonDefaults.extraSmallPressedShape val pressedShape = IconButtonDefaults.extraSmallPressedShape
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
@@ -173,6 +238,7 @@ fun SmallOutlinedIconToggleButton(
} }
} }
} }
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -12,7 +12,12 @@ 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.theme.LegadoTheme 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 io.legado.app.ui.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.Button as MiuixButton
import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
fun SmallTextButton( fun SmallTextButton(
@@ -21,6 +26,17 @@ fun SmallTextButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: () -> Unit onClick: () -> Unit
) { ) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixButton(
onClick = onClick,
modifier = modifier,
colors = MiuixButtonDefaults.buttonColors()
) {
MiuixText(
text = text
)
}
} else {
TextButton( TextButton(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier,
@@ -39,3 +55,4 @@ fun SmallTextButton(
) )
} }
} }
}
@@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -107,12 +108,12 @@ fun TopBarButton(
fun TopbarNavigationButton( fun TopbarNavigationButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack, imageVector: ImageVector = AppIcons.Back,
contentDescription: String? = "返回", contentDescription: String? = "返回",
style: TopBarButtonVariant = TopBarButtonVariant.Filled style: TopBarButtonVariant = TopBarButtonVariant.Filled
) { ) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) { if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixButton( MiuixIconButton(
onClick = onClick, onClick = onClick,
imageVector = imageVector, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
@@ -138,7 +139,7 @@ fun TopBarActionButton(
) { ) {
val enableProgressive = ThemeConfig.enableProgressiveBlur val enableProgressive = ThemeConfig.enableProgressiveBlur
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) { if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixButton( MiuixIconButton(
onClick = onClick, onClick = onClick,
imageVector = imageVector, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
@@ -86,7 +86,6 @@ fun SelectionItemCard(
GlassCard( GlassCard(
onClick = onToggleSelection, onClick = onToggleSelection,
modifier = modifier modifier = modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(), .fillMaxWidth(),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
containerColor = animatedContainerColor, containerColor = animatedContainerColor,
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
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.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -17,11 +16,8 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.widget.components.text.AnimatedTextLine
import io.legado.app.ui.widget.components.AnimatedTextLine
import io.legado.app.ui.widget.components.AppIcon
import top.yukonga.miuix.kmp.theme.MiuixTheme
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -40,17 +36,8 @@ fun TextCard(
spacing: Dp = 4.dp, spacing: Dp = 4.dp,
textStyle: TextStyle = LegadoTheme.typography.labelSmallEmphasized textStyle: TextStyle = LegadoTheme.typography.labelSmallEmphasized
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine) val defaultBackground = LegadoTheme.colorScheme.surfaceContainer
val defaultContent = LegadoTheme.colorScheme.onSurface
val defaultBackground = if (isMiuix)
MiuixTheme.colorScheme.surfaceContainer
else
MaterialTheme.colorScheme.primaryContainer
val defaultContent = if (isMiuix)
MiuixTheme.colorScheme.onSurface
else
MaterialTheme.colorScheme.primary
val finalBackgroundColor = backgroundColor ?: defaultBackground val finalBackgroundColor = backgroundColor ?: defaultBackground
val finalContentColor = contentColor ?: defaultContent val finalContentColor = contentColor ?: defaultContent
@@ -7,17 +7,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Update
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable @Composable
fun BookshelfCover( fun BookshelfCover(
@@ -31,16 +27,6 @@ fun BookshelfCover(
sourceOrigin: String? = null, sourceOrigin: String? = null,
onLoadFinish: (() -> Unit)? = null onLoadFinish: (() -> Unit)? = null
) { ) {
val backgroundColor = if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixTheme.colorScheme.primaryContainer.copy(alpha = 0.2f)
} else {
MaterialTheme.colorScheme.secondaryContainer
}
val contentColor = if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSecondaryContainer
}
Box(modifier = modifier) { Box(modifier = modifier) {
BookCover( BookCover(
name = name, name = name,
@@ -58,8 +44,6 @@ fun BookshelfCover(
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(2.dp), .padding(2.dp),
backgroundColor = backgroundColor,
contentColor = contentColor,
cornerRadius = 4.dp, cornerRadius = 4.dp,
horizontalPadding = 4.dp, horizontalPadding = 4.dp,
verticalPadding = 0.dp verticalPadding = 0.dp
@@ -41,12 +41,13 @@ import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController import com.github.skydoves.colorpicker.compose.rememberColorPickerController
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ColorPickerSheet( fun ColorPickerSheet(
show: Boolean,
initialColor: Int, initialColor: Int,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onColorSelected: (Int) -> Unit onColorSelected: (Int) -> Unit
@@ -80,7 +81,7 @@ fun ColorPickerSheet(
) )
} }
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -14,14 +13,26 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable @Composable
fun PillDivider( fun PillDivider(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
thickness: Dp = 2.dp, thickness: Dp = 2.dp,
widthFraction: Float = 0.2f, widthFraction: Float = 0.2f,
color: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f) color: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(4.dp)
.background(MiuixTheme.colorScheme.secondaryContainer)
)
} else {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@@ -37,3 +48,4 @@ fun PillDivider(
) )
} }
} }
}
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -24,8 +23,8 @@ fun PillHeaderDivider(
title: String, title: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dotSize: Dp = 2.dp, dotSize: Dp = 2.dp,
dotColor: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f), dotColor: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
titleColor: Color = MaterialTheme.colorScheme.primary titleColor: Color = LegadoTheme.colorScheme.primary
) { ) {
Row( Row(
modifier = modifier modifier = modifier
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
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.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
@@ -17,7 +16,6 @@ import androidx.compose.material.icons.filled.CloudUpload
import androidx.compose.material.icons.filled.EditNote import androidx.compose.material.icons.filled.EditNote
import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,7 +26,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
enum class FilePickerSheetMode { enum class FilePickerSheetMode {
@@ -38,6 +37,7 @@ enum class FilePickerSheetMode {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun FilePickerSheet( fun FilePickerSheet(
show: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
title: String = stringResource(R.string.select_operation), title: String = stringResource(R.string.select_operation),
onSelectSysDir: (() -> Unit)? = null, onSelectSysDir: (() -> Unit)? = null,
@@ -47,22 +47,17 @@ fun FilePickerSheet(
onUpload: (() -> Unit)? = null, onUpload: (() -> Unit)? = null,
allowExtensions: Array<String>? = null, allowExtensions: Array<String>? = null,
) { ) {
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
title = title,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
containerColor = { it.surface } containerColor = LegadoTheme.colorScheme.surface
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth(),
.padding(horizontal = 16.dp, vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
AppText(
text = title,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 20.dp)
)
FlowRow( FlowRow(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -127,7 +122,7 @@ private fun RowScope.FilePickerOptionCard(
.weight(1f) .weight(1f)
.height(100.dp), .height(100.dp),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceContainerLow, color = LegadoTheme.colorScheme.surfaceContainerLow,
tonalElevation = 2.dp tonalElevation = 2.dp
) { ) {
Column( Column(
@@ -135,11 +130,11 @@ private fun RowScope.FilePickerOptionCard(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( AppIcon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp), modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary tint = LegadoTheme.colorScheme.primary
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
AppText( AppText(
@@ -0,0 +1,88 @@
package io.legado.app.ui.widget.components.icon
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
/**
* 1. 基础 AppIcon 实现
*/
@Composable
fun AppIcon(
painter: Painter,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified,
) {
val defaultTintColor = LegadoTheme.colorScheme.onSurface
val finalTint = tint.takeOrElse { defaultTintColor }
val colorFilter = remember(finalTint) {
if (finalTint == Color.Unspecified) null else ColorFilter.tint(finalTint)
}
val semantics = if (contentDescription != null) {
Modifier.semantics {
this.contentDescription = contentDescription
this.role = Role.Image
}
} else {
Modifier
}
Box(
modifier
.defaultIconSize(painter)
.paint(painter, colorFilter = colorFilter, contentScale = ContentScale.Fit)
.then(semantics)
)
}
@Composable
fun AppIcon(
imageVector: ImageVector?,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified,
) {
if (imageVector == null) return
AppIcon(
painter = rememberVectorPainter(imageVector),
contentDescription = contentDescription,
modifier = modifier,
tint = tint,
)
}
/**
* 3. 辅助扩展函数:处理图标的默认尺寸
* M3 和 MIUIX 内部都有 defaultSizeFor,如果 painter 没有固有尺寸,默认设为 24.dp
*/
private fun Modifier.defaultIconSize(painter: Painter): Modifier {
return this.then(
if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isUnspecified) {
Modifier.size(24.dp)
} else {
Modifier
}
)
}
@@ -0,0 +1,54 @@
package io.legado.app.ui.widget.components.icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Replay
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.basic.Search
import top.yukonga.miuix.kmp.icon.extended.Back
import top.yukonga.miuix.kmp.icon.extended.Close
import top.yukonga.miuix.kmp.icon.extended.Refresh
/**
* 应用全局图标映射层
*/
object AppIcons {
private val isMiuix: Boolean
@Composable
get() = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val Search: ImageVector
@Composable
get() = if (isMiuix) {
MiuixIcons.Basic.Search
} else {
Icons.Default.Search
}
val Close: ImageVector
@Composable
get() = if (isMiuix) {
MiuixIcons.Close
} else {
Icons.Default.Clear
}
val Back: ImageVector
@Composable
get() = if (isMiuix) {
MiuixIcons.Back
} else {
Icons.AutoMirrored.Filled.ArrowBack
}
val Replay: ImageVector
@Composable
get() = if (isMiuix) MiuixIcons.Refresh ?: Icons.Default.Replay else Icons.Default.Replay
}
@@ -1,10 +1,6 @@
package io.legado.app.ui.widget.components.importComponents package io.legado.app.ui.widget.components.importComponents
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -26,16 +22,10 @@ import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip import androidx.compose.material3.AssistChip
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@@ -44,7 +34,6 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
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
@@ -59,9 +48,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AnimatedText
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AnimatedText
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -116,31 +107,42 @@ fun SourceInputDialog(
) )
} }
//TODO: 动画
@SuppressLint("ConfigurationScreenWidthHeight") @SuppressLint("ConfigurationScreenWidthHeight")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun <T> BatchImportDialog( fun <T> BatchImportDialog(
title: String, title: String,
importState: BaseImportUiState.Success<T>, importState: BaseImportUiState<T>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onConfirm: (List<T>) -> Unit, // 返回选中的原始数据列表 onConfirm: (List<T>) -> Unit,
onToggleItem: (index: Int) -> Unit, onToggleItem: (index: Int) -> Unit,
onToggleAll: (isSelected: Boolean) -> Unit, onToggleAll: (isSelected: Boolean) -> Unit,
onItemInfoClick: (index: Int) -> Unit = {}, onItemInfoClick: (index: Int) -> Unit = {},
// 插槽:允许调用方自定义顶部菜单
topBarActions: @Composable RowScope.() -> Unit = {}, topBarActions: @Composable RowScope.() -> Unit = {},
// 插槽:自定义每一行的显示内容 itemTitle: (data: T) -> String,
itemContent: @Composable (data: T, status: ImportStatus) -> Unit itemSubtitle: (data: T) -> String? = { null }
) { ) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val show = importState is BaseImportUiState.Success<T>
var cachedState by remember { mutableStateOf<BaseImportUiState.Success<T>?>(null) }
if (importState is BaseImportUiState.Success<T>) {
cachedState = importState
}
if (!show && cachedState == null) return
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val selectedCount = importState.items.count { it.isSelected }
val totalCount = importState.items.size
GlassModalBottomSheet( val currentState = cachedState!!
val selectedCount = currentState.items.count { it.isSelected }
val totalCount = currentState.items.size
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
containerColor = { it.surfaceContainer } containerColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
AppScaffold( AppScaffold(
modifier = Modifier modifier = Modifier
@@ -171,44 +173,34 @@ fun <T> BatchImportDialog(
ImportBottomBar( ImportBottomBar(
selectedCount = selectedCount, selectedCount = selectedCount,
totalCount = totalCount, totalCount = totalCount,
onToggleSelectAll = { isAll -> onToggleAll(isAll) }, onToggleSelectAll = onToggleAll,
onConfirm = { onConfirm = {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
val selectedData = val selectedData =
importState.items.filter { it.isSelected }.map { it.data } currentState.items.filter { it.isSelected }.map { it.data }
onConfirm(selectedData) onConfirm(selectedData)
}
}
}, },
onCancel = { onCancel = onDismissRequest
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
onDismissRequest()
}
}
}
) )
} }
) { padding -> ) { padding ->
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier.padding(padding),
.padding(padding),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
itemsIndexed(importState.items, key = { _, item -> item.data.hashCode() }) { index, itemWrapper -> itemsIndexed(
currentState.items,
key = { _, item -> item.data.hashCode() }) { index, itemWrapper ->
ImportItemRow( ImportItemRow(
title = itemTitle(itemWrapper.data),
subtitle = itemSubtitle(itemWrapper.data),
isSelected = itemWrapper.isSelected, isSelected = itemWrapper.isSelected,
status = itemWrapper.status, status = itemWrapper.status,
onClick = { onToggleItem(index) }, onClick = { onToggleItem(index) },
onInfoClick = { onInfoClick = {
scope.launch { scope.launch { snackbarHostState.showSnackbar("其实还没写桀桀桀") }
snackbarHostState.showSnackbar("其实还没写桀桀桀")
}
onItemInfoClick(index) onItemInfoClick(index)
}, }
content = { itemContent(itemWrapper.data, itemWrapper.status) }
) )
} }
} }
@@ -216,45 +208,23 @@ fun <T> BatchImportDialog(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ImportItemRow( fun ImportItemRow(
title: String,
subtitle: String? = null,
isSelected: Boolean, isSelected: Boolean,
status: ImportStatus, status: ImportStatus,
onClick: () -> Unit, onClick: () -> Unit,
onInfoClick: () -> Unit, onInfoClick: () -> Unit
content: @Composable () -> Unit
) { ) {
val containerColor by animateColorAsState( SelectionItemCard(
targetValue = if (isSelected) title = title,
MaterialTheme.colorScheme.secondaryContainer subtitle = subtitle,
else isSelected = isSelected,
MaterialTheme.colorScheme.surface, inSelectionMode = true,
animationSpec = tween( onToggleSelection = onClick,
durationMillis = 200, modifier = Modifier.padding(vertical = 4.dp),
easing = FastOutSlowInEasing trailingAction = {
),
label = "CardColor"
)
Card(
onClick = onClick,
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors(
containerColor = containerColor
)
) {
ListItem(
modifier = Modifier.animateContentSize(),
headlineContent = { content() },
leadingContent = {
Checkbox(
checked = isSelected,
onCheckedChange = null
)
},
supportingContent = {
AppText( AppText(
text = when (status) { text = when (status) {
ImportStatus.New -> "新增" ImportStatus.New -> "新增"
@@ -268,22 +238,17 @@ private fun ImportItemRow(
ImportStatus.Update -> MaterialTheme.colorScheme.secondary ImportStatus.Update -> MaterialTheme.colorScheme.secondary
ImportStatus.Error -> MaterialTheme.colorScheme.error ImportStatus.Error -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.outline else -> MaterialTheme.colorScheme.outline
}
)
}, },
trailingContent = { modifier = Modifier.padding(end = 4.dp)
IconButton(onClick = onInfoClick) { )
Icon(
imageVector = Icons.Default.Info, SmallIconButton(
onClick = onInfoClick,
icon = Icons.Default.Info,
contentDescription = "详情" contentDescription = "详情"
) )
} }
},
colors = ListItemDefaults.colors(
containerColor = Color.Transparent
) )
)
}
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -46,6 +46,7 @@ import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastLastOrNull import androidx.compose.ui.util.fastLastOrNull
import androidx.compose.ui.util.fastMaxBy import androidx.compose.ui.util.fastMaxBy
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -67,7 +68,7 @@ fun VerticalFastScroller(
listState: LazyListState, listState: LazyListState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
thumbAllowed: () -> Boolean = { true }, thumbAllowed: () -> Boolean = { true },
thumbColor: Color = MaterialTheme.colorScheme.primary, thumbColor: Color = LegadoTheme.colorScheme.primary,
topContentPadding: Dp = Dp.Hairline, topContentPadding: Dp = Dp.Hairline,
bottomContentPadding: Dp = Dp.Hairline, bottomContentPadding: Dp = Dp.Hairline,
endContentPadding: Dp = Dp.Hairline, endContentPadding: Dp = Dp.Hairline,
@@ -3,6 +3,8 @@ package io.legado.app.ui.widget.components.menuItem
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.MaterialExpressiveTheme
@@ -15,8 +17,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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.ThemeResolver
import io.legado.app.ui.theme.rememberOpaqueColorScheme import io.legado.app.ui.theme.rememberOpaqueColorScheme
import top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.extra.SuperListPopup
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -24,11 +29,29 @@ fun RoundDropdownMenu(
expanded: Boolean, expanded: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
// M3 专属视觉参数,Miuix 模式下会优雅降级(忽略)
shape: Shape = MaterialTheme.shapes.medium, shape: Shape = MaterialTheme.shapes.medium,
shadowElevation: Dp = 4.dp, shadowElevation: Dp = 4.dp,
verticalSpacing: Dp = 8.dp, verticalSpacing: Dp = 8.dp,
content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
SuperListPopup(
show = expanded,
onDismissRequest = onDismissRequest,
popupModifier = modifier
) {
ListPopupColumn {
Column() {
Spacer(Modifier.height(12.dp))
content(onDismissRequest)
Spacer(Modifier.height(12.dp))
}
}
}
} else {
val colorScheme = rememberOpaqueColorScheme() val colorScheme = rememberOpaqueColorScheme()
DropdownMenu( DropdownMenu(
@@ -53,3 +76,4 @@ fun RoundDropdownMenu(
} }
} }
} }
}
@@ -1,6 +1,8 @@
package io.legado.app.ui.widget.components.menuItem package io.legado.app.ui.widget.components.menuItem
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -11,40 +13,104 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuDefaults import androidx.compose.material3.MenuDefaults
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.rememberOpaqueColorScheme import io.legado.app.ui.theme.rememberOpaqueColorScheme
import io.legado.app.ui.widget.components.icon.AppIcon
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
fun RoundDropdownMenuItem( fun RoundDropdownMenuItem(
text: @Composable () -> Unit, text: String,
color: Color = LegadoTheme.colorScheme.surface,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isSelected: Boolean = false,
leadingIcon: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null,
enabled: Boolean = true, enabled: Boolean = true,
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding, contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
interactionSource: MutableInteractionSource? = null, interactionSource: MutableInteractionSource? = null,
) { ) {
val colorScheme = rememberOpaqueColorScheme() val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val interaction = interactionSource ?: remember { MutableInteractionSource() } val interaction = interactionSource ?: remember { MutableInteractionSource() }
val contentColor = if (isMiuix) {
if (enabled) MaterialTheme.colorScheme.onSurface val backgroundColor = if (isSelected) {
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) LegadoTheme.colorScheme.primaryContainer
} else {
Color.Transparent
}
val contentColor = if (isSelected) {
LegadoTheme.colorScheme.onPrimaryContainer
} else {
LegadoTheme.colorScheme.onSurface
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
.fillMaxWidth()
.drawBehind { drawRect(backgroundColor) }
.clickable(
interactionSource = interaction,
indication = androidx.compose.foundation.LocalIndication.current,
enabled = enabled,
onClick = onClick
)
.padding(horizontal = 20.dp, vertical = 12.dp)
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides MiuixTheme.textStyles.body1
) {
if (leadingIcon != null) {
leadingIcon()
Spacer(Modifier.width(12.dp))
}
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.CenterStart
) {
MiuixText(
text = text,
fontWeight = FontWeight.Medium
)
}
if (trailingIcon != null) {
Spacer(Modifier.width(12.dp))
trailingIcon()
}
}
}
} else {
val colorScheme = rememberOpaqueColorScheme()
val contentColor = if (enabled) {
MaterialTheme.colorScheme.onSurface
} else {
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
}
Surface( Surface(
onClick = onClick, onClick = onClick,
@@ -55,10 +121,6 @@ fun RoundDropdownMenuItem(
shape = MaterialTheme.shapes.small, shape = MaterialTheme.shapes.small,
color = colorScheme.surface, color = colorScheme.surface,
interactionSource = interaction interactionSource = interaction
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides LegadoTheme.typography.labelLarge
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -67,17 +129,20 @@ fun RoundDropdownMenuItem(
.widthIn(min = 120.dp), .widthIn(min = 120.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (leadingIcon != null) { if (leadingIcon != null) {
leadingIcon() leadingIcon()
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(12.dp))
} }
Box( Box(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
contentAlignment = Alignment.CenterStart contentAlignment = Alignment.CenterStart
) { ) {
text() Text(
modifier = Modifier.widthIn(max = 200.dp),
text = text,
style = LegadoTheme.typography.bodyMediumEmphasized
)
} }
if (trailingIcon != null) { if (trailingIcon != null) {
@@ -94,9 +159,9 @@ fun MenuItemIcon(
imageVector: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null, contentDescription: String? = null,
tint: Color = LocalContentColor.current tint: Color = Color.Unspecified
) { ) {
Icon( AppIcon(
imageVector = imageVector, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = modifier.size(18.dp), modifier = modifier.size(18.dp),
@@ -0,0 +1,138 @@
package io.legado.app.ui.widget.components.modalBottomSheet
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.Text
import androidx.compose.material3.Typography
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.rememberOpaqueColorScheme
import top.yukonga.miuix.kmp.extra.WindowBottomSheet
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AppModalBottomSheet(
show: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
title: String? = null,
startAction: @Composable (() -> Unit)? = null,
endAction: @Composable (() -> Unit)? = null,
containerColor: Color = LegadoTheme.colorScheme.surfaceContainerLow,
content: @Composable ColumnScope.() -> Unit
) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
WindowBottomSheet(
show = show,
modifier = modifier,
title = title,
startAction = startAction,
endAction = endAction,
insideMargin = DpSize(16.dp, 12.dp),
backgroundColor = containerColor,
onDismissRequest = onDismissRequest,
onDismissFinished = onDismissRequest,
enableWindowDim = true,
allowDismiss = true
) {
Column(
modifier = Modifier.fillMaxWidth(),
content = content
)
}
} else {
if (show) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val colorScheme = rememberOpaqueColorScheme()
val density = LocalDensity.current
val maxHeight = with(density) {
LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
}
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
containerColor = containerColor,
contentColor = colorScheme.onSurface,
dragHandle = { BottomSheetDefaults.DragHandle() }
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.heightIn(max = maxHeight)
.animateContentSize()
.then(modifier)
) {
val hasHeader =
!title.isNullOrEmpty() || startAction != null || endAction != null
if (hasHeader) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
contentAlignment = Alignment.Center
) {
if (startAction != null) {
Box(modifier = Modifier.align(Alignment.CenterStart)) {
startAction()
}
}
if (!title.isNullOrEmpty()) {
Text(
text = title,
style = LegadoTheme.typography.titleMediumEmphasized,
color = colorScheme.onSurface,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 56.dp)
)
}
if (endAction != null) {
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
endAction()
}
}
}
}
content()
}
}
}
}
}
}
@@ -1,64 +0,0 @@
package io.legado.app.ui.widget.components.modalBottomSheet
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.SheetState
import androidx.compose.material3.Typography
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import io.legado.app.ui.theme.rememberOpaqueColorScheme
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun GlassModalBottomSheet(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
sheetState: SheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
containerColor: (ColorScheme) -> Color = { it.surfaceContainerLow },
content: @Composable ColumnScope.() -> Unit
) {
val colorScheme = rememberOpaqueColorScheme()
val density = LocalDensity.current
val maxHeight = with(density) {
LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
}
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
containerColor = containerColor(colorScheme),
contentColor = colorScheme.onSurface
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = maxHeight)
.animateContentSize()
.then(modifier),
content = content
)
}
}
}
@@ -35,7 +35,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -52,6 +52,7 @@ data class RuleEditFields(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun <T> RuleEditSheet( fun <T> RuleEditSheet(
show: Boolean,
rule: T?, rule: T?,
title: String, title: String,
label1: String, label1: String,
@@ -74,7 +75,8 @@ fun <T> RuleEditSheet(
fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule) fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
@@ -1,13 +1,12 @@
package io.legado.app.ui.widget.components.settingItem package io.legado.app.ui.widget.components.settingItem
import androidx.compose.foundation.layout.Arrangement import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.Add
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Remove
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -20,17 +19,22 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
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.button.SmallOutlinedIconButton 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.ValueStepper
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.extra.SuperSwitch
import top.yukonga.miuix.kmp.extra.WindowDropdown
import top.yukonga.miuix.kmp.basic.Slider as MiuixSlider
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -41,11 +45,34 @@ fun CompactDropdownSettingItem(
entryValues: Array<String>, entryValues: Array<String>,
description: String? = null, description: String? = null,
imageVector: ImageVector? = null, imageVector: ImageVector? = null,
color: Color? = MaterialTheme.colorScheme.surface, color: Color? = LegadoTheme.colorScheme.surface,
shape: Shape = MaterialTheme.shapes.small, shape: Shape = MaterialTheme.shapes.small,
onValueChange: (String) -> Unit onValueChange: (String) -> Unit
) { ) {
val currentEntry = displayEntries.getOrNull(entryValues.indexOf(selectedValue)) ?: selectedValue if (ThemeResolver.isMiuixEngine(composeEngine)) {
val selectedIndex = entryValues.indexOf(selectedValue).coerceAtLeast(0)
val spinnerItems = displayEntries.toList()
WindowDropdown(
title = title,
summary = description,
items = spinnerItems,
selectedIndex = selectedIndex,
startAction = imageVector?.let { icon ->
{
Icon(
imageVector = icon,
contentDescription = null
)
}
},
onSelectedIndexChange = { index ->
onValueChange(entryValues[index])
}
)
} else {
val currentEntry =
displayEntries.getOrNull(entryValues.indexOf(selectedValue)) ?: selectedValue
SettingItem( SettingItem(
title = title, title = title,
@@ -66,7 +93,7 @@ fun CompactDropdownSettingItem(
dropdownMenu = { onDismiss -> dropdownMenu = { onDismiss ->
displayEntries.forEachIndexed { index, display -> displayEntries.forEachIndexed { index, display ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(display) }, text = display,
onClick = { onClick = {
onValueChange(entryValues[index]) onValueChange(entryValues[index])
onDismiss() onDismiss()
@@ -85,6 +112,7 @@ fun CompactDropdownSettingItem(
} }
) )
} }
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -104,6 +132,45 @@ fun CompactSliderSettingItem(
var sliderValue by remember(value) { mutableFloatStateOf(value) } var sliderValue by remember(value) { mutableFloatStateOf(value) }
var displayValue by remember(value) { mutableFloatStateOf(value) } var displayValue by remember(value) { mutableFloatStateOf(value) }
if (ThemeResolver.isMiuixEngine(composeEngine)) {
Column(modifier = Modifier.fillMaxWidth()) {
BasicComponent(
title = title,
summary = description,
onClick = { expanded = !expanded },
endActions = {
ValueStepper(
value = value,
displayValue = displayValue,
valueRange = valueRange,
onValueChange = onValueChange
)
}
)
AnimatedVisibility(visible = expanded) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
MiuixSlider(
value = sliderValue,
onValueChange = {
sliderValue = it
displayValue = it
},
onValueChangeFinished = {
onValueChange(sliderValue)
},
valueRange = valueRange,
steps = steps,
modifier = Modifier.fillMaxWidth()
)
}
}
}
} else {
SettingItem( SettingItem(
title = title, title = title,
description = description, description = description,
@@ -113,33 +180,12 @@ fun CompactSliderSettingItem(
expanded = expanded, expanded = expanded,
onExpandChange = { expanded = it }, onExpandChange = { expanded = it },
trailingContent = { trailingContent = {
Row( ValueStepper(
verticalAlignment = Alignment.CenterVertically, value = value,
horizontalArrangement = Arrangement.spacedBy(8.dp) displayValue = displayValue,
) { valueRange = valueRange,
SmallOutlinedIconButton( onValueChange = onValueChange
onClick = {
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
onValueChange(newValue)
},
icon = Icons.Default.Remove
) )
TextCard(
cornerRadius = 8.dp,
horizontalPadding = 8.dp,
verticalPadding = 4.dp,
text = displayValue.toInt().toString(),
backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
contentColor = MaterialTheme.colorScheme.onSurface
)
SmallOutlinedIconButton(
onClick = {
val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
onValueChange(newValue)
},
icon = Icons.Default.Add
)
}
}, },
expandContent = { expandContent = {
Slider( Slider(
@@ -157,6 +203,7 @@ fun CompactSliderSettingItem(
) )
} }
) )
}
LaunchedEffect(value) { LaunchedEffect(value) {
if (!expanded) { if (!expanded) {
@@ -177,6 +224,15 @@ fun CompactSwitchSettingItem(
enabled: Boolean = true, enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit onCheckedChange: (Boolean) -> Unit
) { ) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
SuperSwitch(
title = title,
summary = description,
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled,
)
} else {
SettingItem( SettingItem(
title = title, title = title,
description = description, description = description,
@@ -194,3 +250,4 @@ fun CompactSwitchSettingItem(
} }
) )
} }
}
@@ -11,7 +11,6 @@ import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.SpinnerEntry import top.yukonga.miuix.kmp.basic.SpinnerEntry
import top.yukonga.miuix.kmp.extra.SuperSpinner import top.yukonga.miuix.kmp.extra.SuperSpinner
@@ -64,7 +63,7 @@ fun DropdownListSettingItem(
dropdownMenu = { onDismiss -> dropdownMenu = { onDismiss ->
displayEntries.forEachIndexed { index, display -> displayEntries.forEachIndexed { index, display ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(display) }, text = display,
onClick = { onClick = {
onValueChange(entryValues[index]) onValueChange(entryValues[index])
onDismiss() onDismiss()
@@ -6,8 +6,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.AdaptiveSwitch import io.legado.app.ui.widget.components.AdaptiveSwitch
import top.yukonga.miuix.kmp.basic.BasicComponent import top.yukonga.miuix.kmp.extra.SuperSwitch
import top.yukonga.miuix.kmp.basic.Switch
@Composable @Composable
@@ -23,22 +22,12 @@ fun SwitchSettingItem(
val composeEngine = LegadoTheme.composeEngine val composeEngine = LegadoTheme.composeEngine
if (ThemeResolver.isMiuixEngine(composeEngine)) { if (ThemeResolver.isMiuixEngine(composeEngine)) {
val toggleAction = { SuperSwitch(
if (enabled) onCheckedChange(!checked)
}
BasicComponent(
title = title, title = title,
summary = description, summary = description,
enabled = enabled,
onClick = toggleAction,
endActions = {
Switch(
checked = checked, checked = checked,
onCheckedChange = onCheckedChange, onCheckedChange = onCheckedChange,
enabled = enabled enabled = enabled,
)
}
) )
} else { } else {
SettingItem( SettingItem(
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
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.Color import androidx.compose.ui.graphics.Color
@@ -11,12 +12,11 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.TabRowDefaults import top.yukonga.miuix.kmp.basic.TabRowDefaults
import top.yukonga.miuix.kmp.basic.TabRowWithContour import top.yukonga.miuix.kmp.basic.TabRowWithContour
@Composable @Composable
fun AdaptiveTabRow( fun AppTabRow(
tabTitles: List<String>, tabTitles: List<String>,
selectedTabIndex: Int, selectedTabIndex: Int,
onTabSelected: (Int) -> Unit, onTabSelected: (Int) -> Unit,
@@ -48,7 +48,7 @@ fun AdaptiveTabRow(
selected = selectedTabIndex == index, selected = selectedTabIndex == index,
onClick = { onTabSelected(index) }, onClick = { onTabSelected(index) },
text = { text = {
AppText( Text(
text = title, text = title,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components.text
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@@ -22,7 +22,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import io.legado.app.ui.widget.components.text.AppText
@Composable @Composable
fun AnimatedText( fun AnimatedText(
@@ -1,11 +1,11 @@
package io.legado.app.ui.widget.components.text package io.legado.app.ui.widget.components.text
import androidx.compose.material3.LocalTextStyle import androidx.compose.foundation.text.BasicText
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.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -16,10 +16,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import io.legado.app.ui.theme.LegadoTheme.composeEngine import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
fun AppText( fun AppText(
@@ -41,53 +38,41 @@ fun AppText(
onTextLayout: ((TextLayoutResult) -> Unit)? = null, onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle? = null, style: TextStyle? = null,
) { ) {
val baseStyle = style ?: LegadoTheme.typography.bodyMedium
if (ThemeResolver.isMiuixEngine(composeEngine)) { // 2. 获取默认文本色:直接拿你自己封装的 LegadoTheme.colorScheme.onSurface
MiuixText( val defaultTextColor = LegadoTheme.colorScheme.onSurface
text = text,
modifier = modifier, // 3. 颜色降级逻辑:传入的 color -> style 中的 color -> 主题默认色
color = color, val finalTextColor = color.takeOrElse {
fontSize = fontSize, baseStyle.color.takeOrElse { defaultTextColor }
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout,
style = style ?: MiuixTheme.textStyles.main
)
} else {
Text(
text = text,
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout ?: {},
style = style ?: LocalTextStyle.current
)
} }
BasicText(
text = text,
modifier = modifier,
style = baseStyle.merge(
color = finalTextColor,
fontSize = fontSize,
fontWeight = fontWeight,
textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing,
),
onTextLayout = onTextLayout,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines
)
} }
@Composable @Composable
fun AppText( fun AppText(
text: AnnotatedString, text: AnnotatedString, // 接收 AnnotatedString
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
color: Color = Color.Unspecified, color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified, fontSize: TextUnit = TextUnit.Unspecified,
@@ -105,46 +90,36 @@ fun AppText(
onTextLayout: ((TextLayoutResult) -> Unit)? = null, onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle? = null, style: TextStyle? = null,
) { ) {
// 1. 获取基础样式:如果外部没传 style,直接拿你自己封装的 LegadoTheme.typography.bodyMedium
// 这完美避开了 M3 的 LocalTextStyle,且自动适配 Miuix/M3 引擎!
val baseStyle = style ?: LegadoTheme.typography.bodyMedium
if (ThemeResolver.isMiuixEngine(composeEngine)) { // 2. 获取默认文本色:直接拿你自己封装的 LegadoTheme.colorScheme.onSurface
MiuixText( val defaultTextColor = LegadoTheme.colorScheme.onSurface
// 3. 颜色降级逻辑:传入的 color -> style 中的 color -> 主题默认色
val finalTextColor = color.takeOrElse {
baseStyle.color.takeOrElse { defaultTextColor }
}
BasicText(
text = text, text = text,
modifier = modifier, modifier = modifier,
color = color, style = baseStyle.merge(
color = finalTextColor,
fontSize = fontSize, fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight, fontWeight = fontWeight,
fontFamily = fontFamily, textAlign = textAlign ?: TextAlign.Unspecified,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight, lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing,
),
onTextLayout = onTextLayout,
overflow = overflow, overflow = overflow,
softWrap = softWrap, softWrap = softWrap,
maxLines = maxLines, maxLines = maxLines,
minLines = minLines, minLines = minLines
onTextLayout = onTextLayout ?: {},
style = style ?: MiuixTheme.textStyles.main
)
} else {
Text(
text = text,
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout ?: {},
style = style ?: LocalTextStyle.current
) )
} }
}
@@ -10,12 +10,9 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.MoreVert 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.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
@@ -23,12 +20,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
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 io.legado.app.ui.widget.components.AnimatedTextLine
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.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.icon.AppIcons
import io.legado.app.ui.widget.components.list.ListUiState import io.legado.app.ui.widget.components.list.ListUiState
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.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -70,7 +68,7 @@ fun <T> DynamicTopAppBar(
if (isSelecting || onBackClick != null) { if (isSelecting || onBackClick != null) {
TopbarNavigationButton( TopbarNavigationButton(
onClick = { if (isSelecting) onClearSelection() else onBackClick?.invoke() }, onClick = { if (isSelecting) onClearSelection() else onBackClick?.invoke() },
imageVector = if (isSelecting) Icons.Default.Close else Icons.AutoMirrored.Filled.ArrowBack, imageVector = if (isSelecting) AppIcons.Close else AppIcons.Back,
contentDescription = if (isSelecting) "取消选择" else "返回" contentDescription = if (isSelecting) "取消选择" else "返回"
) )
} }
@@ -113,7 +111,6 @@ fun <T> DynamicTopAppBar(
query = state.searchKey, query = state.searchKey,
onQueryChange = onSearchQueryChange, onQueryChange = onSearchQueryChange,
placeholder = searchPlaceholder, placeholder = searchPlaceholder,
leadingIcon = { Icon(searchLeadingIcon, null) },
trailingIcon = searchTrailingIcon, trailingIcon = searchTrailingIcon,
dropdownMenu = searchDropdownMenu dropdownMenu = searchDropdownMenu
) )
@@ -21,8 +21,8 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LocalHazeState import io.legado.app.ui.theme.LocalHazeState
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.widget.components.AdaptiveAnimatedText
import io.legado.app.ui.widget.components.GlassDefaults import io.legado.app.ui.widget.components.GlassDefaults
import io.legado.app.ui.widget.components.text.AdaptiveAnimatedText
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar