[优化] 增加控件

This commit is contained in:
HapeLee
2026-04-03 01:08:51 +08:00
parent 733312fe23
commit dd88417f8a
81 changed files with 2461 additions and 1708 deletions
@@ -15,7 +15,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -91,8 +90,6 @@ fun AboutScreen(
.wrapContentWidth(Alignment.CenterHorizontally))
TextCard(
text = versionName,
backgroundColor = colorScheme.tertiaryContainer,
contentColor = colorScheme.onTertiaryContainer,
cornerRadius = 8.dp,
modifier = Modifier
.fillMaxWidth()
@@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.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.LogUtils
import java.util.Date
@@ -39,12 +39,14 @@ import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppLogSheet(
show: Boolean,
onDismissRequest: () -> Unit
) {
var logs by remember { mutableStateOf(AppLog.logs) }
var showStackTrace by remember { mutableStateOf<String?>(null) }
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
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.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import org.koin.androidx.compose.koinViewModel
@@ -128,7 +127,7 @@ fun AllBookmarkScreen(
onDismissRequest = { showMenu = false }
) {
RoundDropdownMenuItem(
text = { AppText("导出 JSON") },
text = "导出 JSON",
onClick = {
showMenu = false
pendingExportIsMd = false
@@ -136,7 +135,7 @@ fun AllBookmarkScreen(
}
)
RoundDropdownMenuItem(
text = { AppText("导出 Markdown") },
text = "导出 Markdown",
onClick = {
showMenu = false
pendingExportIsMd = true
@@ -238,22 +237,21 @@ fun AllBookmarkScreen(
}
}
if (showBottomSheet && editingBookmark != null) {
BookmarkEditSheet(
bookmark = editingBookmark!!,
onDismiss = {
showBottomSheet = false
editingBookmark = null
},
onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark)
showBottomSheet = false
},
onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete)
showBottomSheet = false
}
)
}
BookmarkEditSheet(
show = showBottomSheet && editingBookmark != null,
bookmark = editingBookmark ?: Bookmark(),
onDismiss = {
showBottomSheet = false
editingBookmark = null
},
onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark)
showBottomSheet = false
},
onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete)
showBottomSheet = false
}
)
}
}
@@ -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.menuItem.RoundDropdownMenu
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.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -180,112 +180,111 @@ fun ExploreShowScreen(
}
}
if (showGridCountSheet) {
GlassModalBottomSheet(
AppModalBottomSheet(
show = showGridCountSheet,
modifier = Modifier
.padding(16.dp),
onDismissRequest = { showGridCountSheet = false }
) {
Row(
modifier = Modifier
.padding(16.dp),
onDismissRequest = { showGridCountSheet = false }
.fillMaxWidth()
.padding(bottom = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
AppText(
text = "布局列数",
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
)
Spacer(modifier = Modifier.width(12.dp))
TextCard(
text = "$gridColumnCount",
textStyle = LegadoTheme.typography.titleSmall,
verticalPadding = 4.dp,
horizontalPadding = 12.dp,
cornerRadius = 12.dp
)
}
Slider(
value = gridColumnCount.toFloat(),
onValueChange = {
val col = it.toInt().coerceIn(1, 10)
viewModel.saveGridCount(col)
},
valueRange = 1f..10f,
steps = 8,
modifier = Modifier.padding(horizontal = 20.dp)
AppText(
text = "布局列数",
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
)
Spacer(Modifier.height(24.dp))
Spacer(modifier = Modifier.width(12.dp))
OutlinedButton(
onClick = { showGridCountSheet = false },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
AppText("完成")
}
TextCard(
text = "$gridColumnCount",
textStyle = LegadoTheme.typography.titleSmall,
verticalPadding = 4.dp,
horizontalPadding = 12.dp,
cornerRadius = 12.dp
)
}
Slider(
value = gridColumnCount.toFloat(),
onValueChange = {
val col = it.toInt().coerceIn(1, 10)
viewModel.saveGridCount(col)
},
valueRange = 1f..10f,
steps = 8,
modifier = Modifier.padding(horizontal = 20.dp)
)
Spacer(Modifier.height(24.dp))
OutlinedButton(
onClick = { showGridCountSheet = false },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
AppText("完成")
}
}
if (showKindSheet) {
GlassModalBottomSheet(
onDismissRequest = { showKindSheet = false }
) {
var kindQuery by remember { mutableStateOf("") }
AppModalBottomSheet(
show = showKindSheet,
onDismissRequest = { showKindSheet = false }
) {
SearchBarSection(
query = kindQuery,
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
onQueryChange = { kindQuery = it },
placeholder = "选择或搜索分类",
)
var kindQuery by remember { mutableStateOf("") }
val filteredKinds = remember(kindQuery, kinds) {
if (kindQuery.isBlank()) kinds
else kinds.filter { kind ->
kind.title.contains(kindQuery, ignoreCase = true) ||
(kind.url?.contains(kindQuery, ignoreCase = true) == true)
}
SearchBarSection(
query = kindQuery,
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
onQueryChange = { kindQuery = it },
placeholder = "选择或搜索分类",
)
val filteredKinds = remember(kindQuery, kinds) {
if (kindQuery.isBlank()) kinds
else kinds.filter { kind ->
kind.title.contains(kindQuery, ignoreCase = true) ||
(kind.url?.contains(kindQuery, ignoreCase = true) == true)
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.weight(1f, fill = false)
) {
itemsIndexed(
items = filteredKinds,
key = { index, kind -> "${kind.url ?: kind.title}_$index" },
span = { _, kind ->
val isClickable = !kind.url.isNullOrBlank()
if (isClickable) GridItemSpan(1) else GridItemSpan(3)
LazyVerticalGrid(
columns = GridCells.Fixed(3),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.weight(1f, fill = false)
) {
itemsIndexed(
items = filteredKinds,
key = { index, kind -> "${kind.url ?: kind.title}_$index" },
span = { _, kind ->
val isClickable = !kind.url.isNullOrBlank()
if (isClickable) GridItemSpan(1) else GridItemSpan(3)
}
) { _, kind ->
KindGridItem(
modifier = Modifier.animateItem(),
kind = kind,
currentTitle = selectedTitle ?: title,
onClick = {
showKindSheet = false
viewModel.switchExploreUrl(kind)
}
) { _, kind ->
KindGridItem(
modifier = Modifier.animateItem(),
kind = kind,
currentTitle = selectedTitle ?: title,
onClick = {
showKindSheet = false
viewModel.switchExploreUrl(kind)
}
)
}
)
}
}
}
AppScaffold(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -339,7 +338,7 @@ fun ExploreShowScreen(
onDismissRequest = { showMenu = false }
) {
RoundDropdownMenuItem(
text = { AppText("全部显示") },
text = "全部显示",
onClick = {
viewModel.setFilterState(BookFilterState.SHOW_ALL)
showMenu = false
@@ -351,7 +350,7 @@ fun ExploreShowScreen(
)
RoundDropdownMenuItem(
text = { AppText("隐藏已在书架的同源书籍") },
text = "隐藏已在书架的同源书籍",
onClick = {
viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
showMenu = false
@@ -363,7 +362,7 @@ fun ExploreShowScreen(
)
RoundDropdownMenuItem(
text = { AppText("隐藏已在书架的非同源书籍") },
text = "隐藏已在书架的非同源书籍",
onClick = {
viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
showMenu = false
@@ -375,7 +374,7 @@ fun ExploreShowScreen(
)
RoundDropdownMenuItem(
text = { AppText("只显示不在书架的书籍") },
text = "只显示不在书架的书籍",
onClick = {
viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
showMenu = false
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -36,9 +35,8 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.data.entities.BookGroup
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.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.CompactSwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
@@ -55,11 +53,12 @@ import java.io.FileOutputStream
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GroupEditSheet(
show: Boolean,
group: BookGroup? = null,
onDismissRequest: () -> Unit,
viewModel: GroupViewModel = koinViewModel()
) {
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
GroupEditContent(
group = group,
onDismissRequest = onDismissRequest,
@@ -108,17 +107,9 @@ fun GroupEditContent(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 16.dp)
.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(
modifier = Modifier.fillMaxWidth(),
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.list.ListScaffold
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.utils.ArchiveUtils
import io.legado.app.utils.ConvertUtils
@@ -199,42 +199,41 @@ fun RemoteBookScreen(
}
}
if (showSheet != null) {
GlassModalBottomSheet(
onDismissRequest = { showSheet = null }
) {
when (val state = showSheet) {
is RemoteBookSheet.Servers -> {
ServersSheetContent(
servers = uiState.servers,
selectedServerId = uiState.selectedServerId,
onSelect = {
viewModel.selectServer(it)
showSheet = null
},
onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
onDelete = { viewModel.deleteServer(it) },
onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
onDefault = {
viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
showSheet = null
}
)
}
is RemoteBookSheet.ServerConfig -> {
ServerConfigSheetContent(
server = state.server,
onSave = {
viewModel.saveServer(it)
showSheet = RemoteBookSheet.Servers
},
onCancel = { showSheet = RemoteBookSheet.Servers }
)
}
else -> {}
AppModalBottomSheet(
show = showSheet != null,
onDismissRequest = { showSheet = null }
) {
when (val state = showSheet) {
is RemoteBookSheet.Servers -> {
ServersSheetContent(
servers = uiState.servers,
selectedServerId = uiState.selectedServerId,
onSelect = {
viewModel.selectServer(it)
showSheet = null
},
onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
onDelete = { viewModel.deleteServer(it) },
onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
onDefault = {
viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
showSheet = null
}
)
}
is RemoteBookSheet.ServerConfig -> {
ServerConfigSheetContent(
server = state.server,
onSave = {
viewModel.saveServer(it)
showSheet = RemoteBookSheet.Servers
},
onCancel = { showSheet = RemoteBookSheet.Servers }
)
}
else -> {}
}
}
@@ -260,7 +259,7 @@ fun RemoteBookScreen(
},
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText("按名称排序") },
text = "按名称排序",
onClick = {
viewModel.toggleSort(RemoteBookSort.Name)
dismiss()
@@ -272,7 +271,7 @@ fun RemoteBookScreen(
}
)
RoundDropdownMenuItem(
text = { AppText("按时间排序") },
text = "按时间排序",
onClick = {
viewModel.toggleSort(RemoteBookSort.Default)
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.Timeline
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
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.ReadRecordDetail
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.EmptyMessageView
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.AppIconButton
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.heatmap.HeatmapCalendarTopBar
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.rememberDaysInRange
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.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.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -164,7 +163,7 @@ fun ReadRecordScreen(
TopbarNavigationButton(onClick = onBackClick)
},
actions = {
IconButton(onClick = {
AppIconButton(onClick = {
val newMode = when (displayMode) {
DisplayMode.AGGREGATE -> DisplayMode.TIMELINE
DisplayMode.TIMELINE -> DisplayMode.LATEST
@@ -178,13 +177,16 @@ fun ReadRecordScreen(
DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List
}
val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate"
Icon(icon, description)
AppIcon(icon, description)
}
IconButton(onClick = { showCalendar = !showCalendar }) {
Icon(Icons.Default.CalendarMonth, contentDescription = "Toggle Calendar")
AppIconButton(onClick = { showCalendar = !showCalendar }) {
AppIcon(
Icons.Default.CalendarMonth,
contentDescription = "Toggle Calendar"
)
}
IconButton(onClick = { showSearch = !showSearch }) {
Icon(Icons.Default.Search, contentDescription = null)
AppIconButton(onClick = { showSearch = !showSearch }) {
AppIcon(Icons.Default.Search, contentDescription = null)
}
},
scrollBehavior = scrollBehavior
@@ -314,24 +316,23 @@ fun ReadRecordScreen(
)
}
if (showCalendar) {
GlassModalBottomSheet(
onDismissRequest = { showCalendar = false }
) {
HeatmapCalendarSection(
dailyReadCounts = state.dailyReadCounts,
dailyReadTimes = state.dailyReadTimes,
selectedDate = state.selectedDate,
onDateSelected = { date ->
viewModel.setSelectedDate(date)
showCalendar = false
},
onClearDate = {
viewModel.setSelectedDate(null)
showCalendar = false
}
)
}
AppModalBottomSheet(
show = showCalendar,
onDismissRequest = { showCalendar = false }
) {
HeatmapCalendarSection(
dailyReadCounts = state.dailyReadCounts,
dailyReadTimes = state.dailyReadTimes,
selectedDate = state.selectedDate,
onDateSelected = { date ->
viewModel.setSelectedDate(date)
showCalendar = false
},
onClearDate = {
viewModel.setSelectedDate(null)
showCalendar = false
}
)
}
@@ -869,28 +870,27 @@ fun DateHeader(
date: String,
dailyTotalTime: Long? = null
) {
val dateText = formatFriendlyDate(date)
SectionHeader(
CollapsibleHeader(
showIcon = false,
isCollapsed = false,
onToggle = { },
title = "",
titleContent = {
Column {
val dateText = formatFriendlyDate(date)
AppText(
text = dateText,
style = LegadoTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = LegadoTheme.colorScheme.primary
)
dailyTotalTime?.let { total ->
AppText(
text = dateText,
style = LegadoTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.secondary
text = "已读 ${formatDuring(total)}",
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.onSurface
)
dailyTotalTime?.let { total ->
AppText(
text = "已读 ${formatDuring(total)}",
style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface
)
}
}
},
detailContent = null,
horizontalArrangement = Arrangement.Start
}
)
}
@@ -912,14 +912,12 @@ fun ReadingSummaryCard(
val totalDurationMinutes = totalTimeMillis / 60000
Card(
GlassCard(
onClick = onClick,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer
)
containerColor = LegadoTheme.colorScheme.surfaceContainer
) {
Row(
modifier = Modifier
@@ -934,7 +932,7 @@ fun ReadingSummaryCard(
AppText(
text = title,
style = LegadoTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary
color = LegadoTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(4.dp))
@@ -947,7 +945,7 @@ fun ReadingSummaryCard(
AppText(
text = "$bookCount",
style = LegadoTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary,
color = LegadoTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
)
AppText(
@@ -965,7 +963,7 @@ fun ReadingSummaryCard(
AppText(
text = "共阅读 $timeString",
style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
color = LegadoTheme.colorScheme.onSurfaceVariant
)
}
@@ -310,7 +310,7 @@ fun SearchHistoryList(
AppText(
text = "搜索历史",
style = LegadoTheme.typography.titleSmallEmphasized,
color = MaterialTheme.colorScheme.primary,
color = LegadoTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.Center)
)
SmallAnimatedActionButton(
@@ -313,33 +313,33 @@ fun TocScreen(
when (pagerState.currentPage) {
0 -> {
RoundDropdownMenuItem(
text = { AppText("使用替换规则") },
text = "使用替换规则",
trailingIcon = {
Checkbox(checked = useReplace, onCheckedChange = null)
},
onClick = { viewModel.toggleUseReplace() }
)
RoundDropdownMenuItem(
text = { AppText("显示字数") },
text = "显示字数",
trailingIcon = {
Checkbox(checked = showWordCount, onCheckedChange = null)
},
onClick = { viewModel.toggleShowWordCount() }
)
RoundDropdownMenuItem(
text = { AppText("反转目录") },
text = "反转目录",
onClick = { viewModel.reverseToc() }
)
PillDivider()
RoundDropdownMenuItem(
text = { AppText("替换规则") },
text = "替换规则",
onClick = {
onOpenReplaceRule(null)
dismiss()
}
)
RoundDropdownMenuItem(
text = { AppText("新建替换规则") },
text = "新建替换规则",
onClick = {
val scopes = mutableListOf<String>()
book?.name?.let { scopes.add(it) }
@@ -359,7 +359,7 @@ fun TocScreen(
if (book?.isLocal == true) {
PillHeaderDivider(title = "本地书籍选项")
RoundDropdownMenuItem(
text = { AppText("本地书籍目录规则") },
text = "本地书籍目录规则",
onClick = {
val intent =
Intent(context, TxtTocRuleActivity::class.java).apply {
@@ -370,7 +370,7 @@ fun TocScreen(
}
)
RoundDropdownMenuItem(
text = { AppText("拆分超长章节") },
text = "拆分超长章节",
trailingIcon = {
Checkbox(
checked = viewModel.isSplitLongChapter,
@@ -387,7 +387,7 @@ fun TocScreen(
else -> {
RoundDropdownMenuItem(
text = { AppText("导出书签为JSON") },
text = "导出书签为JSON",
onClick = {
val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm",
@@ -399,7 +399,7 @@ fun TocScreen(
}
)
RoundDropdownMenuItem(
text = { AppText("导出书签为MarkDown") },
text = "导出书签为MarkDown",
onClick = {
val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm",
@@ -465,13 +465,13 @@ fun TocScreen(
onDismissRequest = { showVolumeMenu = false }
) {
RoundDropdownMenuItem(
text = { AppText("展开所有卷") },
text = "展开所有卷",
onClick = {
viewModel.expandAllVolumes(); showVolumeMenu = false
}
)
RoundDropdownMenuItem(
text = { AppText("收起所有卷") },
text = "收起所有卷",
onClick = {
viewModel.collapseAllVolumes(); showVolumeMenu = false
}
@@ -483,7 +483,7 @@ fun TocScreen(
PillHeaderDivider(title = "快速跳转")
volumeItems.forEach { uiItem ->
RoundDropdownMenuItem(
text = { AppText(uiItem.title) },
text = uiItem.title,
onClick = {
scope.launch {
val targetIndex =
@@ -594,20 +594,19 @@ fun TocScreen(
}
}
if (editingBookmark != null) {
BookmarkEditSheet(
bookmark = editingBookmark!!,
onDismiss = { editingBookmark = null },
onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark)
editingBookmark = null
},
onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete)
editingBookmark = null
}
)
}
BookmarkEditSheet(
show = editingBookmark != null,
bookmark = editingBookmark!!,
onDismiss = { editingBookmark = null },
onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark)
editingBookmark = null
},
onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete)
editingBookmark = null
}
)
}
}
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -44,13 +43,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.base.BaseRuleEvent
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.DraggableSelectionHandler
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.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.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -154,54 +151,49 @@ fun TxtRuleScreen(
)
}
if (showExportSheet) {
FilePickerSheet(
onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export),
onSelectSysDir = {
showExportSheet = false
exportDoc.launch("exportDictRule.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
}
FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export),
onSelectSysDir = {
showExportSheet = false
exportDoc.launch("exportDictRule.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
if (showImportSheet) {
FilePickerSheet(
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_txt_toc_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
}
(importState as? BaseImportUiState.Success<TxtTocRule>)?.let { state ->
BatchImportDialog(
title = "导入词典规则",
importState = state,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
itemContent = { rule, _ ->
Column {
AppText(rule.name, style = LegadoTheme.typography.titleMedium)
AppText(rule.rule, style = LegadoTheme.typography.bodySmall, maxLines = 1)
}
}
)
}
FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_txt_toc_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
BatchImportDialog(
title = "导入词典规则",
importState = importState,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
itemTitle = { rule -> rule.name },
itemSubtitle = { rule ->
rule.rule.takeIf { it.isNotBlank() }
}
)
LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) {
@@ -229,48 +221,47 @@ fun TxtRuleScreen(
)
}
if (showEditSheet) {
RuleEditSheet(
rule = editingRule,
title = stringResource(R.string.txt_toc_rule),
label1 = stringResource(R.string.regex),
label2 = stringResource(R.string.example),
onDismissRequest = {
showEditSheet = false
editingRule = null
},
onSave = { updatedRule ->
//TODO:我很想把他改为自增主键,但为了兼容性日后再说
if (editingRule == null) {
viewModel.insert(updatedRule)
} else {
viewModel.update(updatedRule)
}
showEditSheet = false
editingRule = null
},
onCopy = { viewModel.copyRule(it) },
onPaste = { viewModel.pasteRule() },
toFields = { r ->
RuleEditFields(
name = r?.name ?: "",
rule1 = r?.rule ?: "",
rule2 = r?.example ?: ""
)
},
fromFields = { fields, old ->
old?.copy(
name = fields.name,
rule = fields.rule1,
example = fields.rule2
) ?: TxtTocRule(
name = fields.name,
rule = fields.rule1,
example = fields.rule2
)
RuleEditSheet(
show = showEditSheet,
rule = editingRule,
title = stringResource(R.string.txt_toc_rule),
label1 = stringResource(R.string.regex),
label2 = stringResource(R.string.example),
onDismissRequest = {
showEditSheet = false
editingRule = null
},
onSave = { updatedRule ->
//TODO:我很想把他改为自增主键,但为了兼容性日后再说
if (editingRule == null) {
viewModel.insert(updatedRule)
} else {
viewModel.update(updatedRule)
}
)
}
showEditSheet = false
editingRule = null
},
onCopy = { viewModel.copyRule(it) },
onPaste = { viewModel.pasteRule() },
toFields = { r ->
RuleEditFields(
name = r?.name ?: "",
rule1 = r?.rule ?: "",
rule2 = r?.example ?: ""
)
},
fromFields = { fields, old ->
old?.copy(
name = fields.name,
rule = fields.rule1,
example = fields.rule2
) ?: TxtTocRule(
name = fields.name,
rule = fields.rule1,
example = fields.rule2
)
}
)
RuleListScaffold(
title = if (isPickMode) "选择目录规则" else "目录规则",
@@ -312,7 +303,7 @@ fun TxtRuleScreen(
snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_str)) },
text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
@@ -453,31 +453,30 @@ fun BackupConfigScreen(
)
}
if (showBackupFilePicker) {
FilePickerSheet(
onDismissRequest = { showBackupFilePicker = false },
onSelectSysDir = {
showBackupFilePicker = false
try {
selectBackupPathLauncher.launch(null)
} catch (e: Exception) {
}
FilePickerSheet(
show = showBackupFilePicker,
onDismissRequest = { showBackupFilePicker = false },
onSelectSysDir = {
showBackupFilePicker = false
try {
selectBackupPathLauncher.launch(null)
} catch (e: Exception) {
}
)
}
}
)
if (showRestoreFilePicker) {
FilePickerSheet(
onDismissRequest = { showRestoreFilePicker = false },
onSelectSysDir = {
showRestoreFilePicker = false
try {
backupAndSelectLauncher.launch(null)
} catch (e: Exception) {
}
FilePickerSheet(
show = showRestoreFilePicker,
onDismissRequest = { showRestoreFilePicker = false },
onSelectSysDir = {
showRestoreFilePicker = false
try {
backupAndSelectLauncher.launch(null)
} catch (e: Exception) {
}
)
}
}
)
if (showRestoreSheet && backupNames.isNotEmpty()) {
AlertDialog(
@@ -246,17 +246,17 @@ fun CoverConfigScreen(
}
}
if (showCoverRuleSheet) {
CoverRuleConfigSheet(onDismissRequest = { showCoverRuleSheet = false })
}
CoverRuleConfigSheet(
show = showCoverRuleSheet,
onDismissRequest = { showCoverRuleSheet = false }
)
manageKey?.let { key ->
CoverManageSheet(
preferenceKey = key,
onDismissRequest = { manageKey = null },
viewModel = viewModel
)
}
CoverManageSheet(
show = manageKey != null,
preferenceKey = manageKey!!,
onDismissRequest = { manageKey = null },
viewModel = viewModel
)
showColorPickerByField?.let { field ->
val initialColor = when (field) {
@@ -268,6 +268,7 @@ fun CoverConfigScreen(
}
ColorPickerSheet(
show = showColorPickerByField != null,
initialColor = initialColor,
onDismissRequest = { showColorPickerByField = null },
onColorSelected = { color ->
@@ -37,12 +37,13 @@ import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.ui.theme.LegadoTheme
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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CoverManageSheet(
show: Boolean,
preferenceKey: String,
onDismissRequest: () -> Unit,
viewModel: CoverConfigViewModel
@@ -62,7 +63,8 @@ fun CoverManageSheet(
}
val coverList = currentCovers.split(",").filter { it.isNotBlank() }
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -142,14 +144,13 @@ fun CoverManageSheet(
}
}
if (showFilePicker) {
FilePickerSheet(
onDismissRequest = { showFilePicker = false },
onSelectSysFiles = {
selectImages.launch("image/*")
showFilePicker = false
},
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
)
}
FilePickerSheet(
show = showFilePicker,
onDismissRequest = { showFilePicker = false },
onSelectSysFiles = {
selectImages.launch("image/*")
showFilePicker = false
},
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.model.BookCover
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.text.AppText
import io.legado.app.utils.toastOnUi
@@ -36,6 +36,7 @@ import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CoverRuleConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit
) {
var enable by remember { mutableStateOf(false) }
@@ -51,7 +52,8 @@ fun CoverRuleConfigSheet(
coverRule = rule.coverRule
}
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import io.legado.app.ui.widget.components.text.AppText
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
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.widget.components.checkBox.CheckboxGroupContainer
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.text.AppText
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CheckSourceBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel = koinViewModel(),
onDismiss: () -> Unit
) {
val context = LocalContext.current
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss
) {
Column(
@@ -42,7 +42,7 @@ import io.legado.app.help.DirectLinkUpload
import io.legado.app.lib.dialogs.selector
import io.legado.app.ui.theme.LegadoTheme
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.utils.GSON
import io.legado.app.utils.fromJsonObject
@@ -53,6 +53,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DirectLinkUploadBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel,
onDismiss: () -> Unit
) {
@@ -64,7 +65,8 @@ fun DirectLinkUploadBottomSheet(
viewModel.initDirectLinkRule()
}
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss
) {
Column(
@@ -398,33 +398,30 @@ fun OtherConfigScreen(
}
}
if (showFilePicker) {
FilePickerSheet(
onDismissRequest = { showFilePicker = false },
onSelectSysDir = {
showFilePicker = false
try {
selectDocTree.launch(null)
} catch (e: Exception) {
FilePickerSheet(
show = showFilePicker,
onDismissRequest = { showFilePicker = false },
onSelectSysDir = {
showFilePicker = false
try {
selectDocTree.launch(null)
} catch (e: Exception) {
}
}
)
}
}
)
if (showCheckSourceSheet) {
CheckSourceBottomSheet(
viewModel = viewModel,
onDismiss = { showCheckSourceSheet = false }
)
}
CheckSourceBottomSheet(
show = showCheckSourceSheet,
viewModel = viewModel,
onDismiss = { showCheckSourceSheet = false }
)
if (showDirectLinkUploadSheet) {
DirectLinkUploadBottomSheet(
viewModel = viewModel,
onDismiss = { showDirectLinkUploadSheet = false }
)
}
DirectLinkUploadBottomSheet(
show = showDirectLinkUploadSheet,
viewModel = viewModel,
onDismiss = { showDirectLinkUploadSheet = false }
)
if (showClearCacheDialog) {
ConfirmDialog(
@@ -23,18 +23,19 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PageKeySheet(
show: Boolean,
onDismissRequest: () -> Unit
) {
var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) }
var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) }
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column(
modifier = Modifier.Companion
.fillMaxWidth()
@@ -320,7 +320,8 @@ fun ReadConfigScreen(
}
}
if (showPageKeySheet) {
PageKeySheet(onDismissRequest = { showPageKeySheet = false })
}
PageKeySheet(
show = showPageKeySheet,
onDismissRequest = { showPageKeySheet = false }
)
}
@@ -32,7 +32,7 @@ import coil.compose.AsyncImage
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
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 kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@@ -40,6 +40,7 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BackgroundImageManageSheet(
show: Boolean,
isDarkTheme: Boolean,
onDismissRequest: () -> Unit,
viewModel: ThemeConfigViewModel = koinViewModel()
@@ -65,7 +66,8 @@ fun BackgroundImageManageSheet(
ThemeConfig.bgImageLight
}
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -140,14 +142,13 @@ fun BackgroundImageManageSheet(
}
}
if (showFilePicker) {
FilePickerSheet(
onDismissRequest = { showFilePicker = false },
onSelectSysFile = {
selectImage.launch("image/*")
showFilePicker = false
},
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
)
}
FilePickerSheet(
show = showFilePicker,
onDismissRequest = { showFilePicker = false },
onSelectSysFile = {
selectImage.launch("image/*")
showFilePicker = false
},
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.LauncherW
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.utils.getCompatDrawable
import splitties.init.appCtx
@@ -47,6 +47,7 @@ import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LauncherIconPickerSheet(
show: Boolean,
selectedValue: String,
onDismissRequest: () -> Unit,
onValueChange: (String) -> Unit
@@ -54,7 +55,8 @@ fun LauncherIconPickerSheet(
val context = LocalContext.current
val icons = LauncherIcons.list
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -15,14 +15,14 @@ object ThemeConfig {
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 paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot")
//m3 or miuix
var composeEngine by prefDelegate(PreferKey.composeEngine, "m3")
var composeEngine by prefDelegate(PreferKey.composeEngine, "material")
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -44,6 +45,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.ToggleButton
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.toastOnUi
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)
@Composable
@@ -137,9 +143,10 @@ fun ThemeConfigScreen(
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
val composeEngine = remember { ThemeConfig.composeEngine }
val isMiuixEngine =
remember(composeEngine) { ThemeResolver.isMiuixEngine(composeEngine) }
val composeEngine = ThemeConfig.composeEngine
val isMiuixEngine = remember(composeEngine) {
ThemeResolver.isMiuixEngine(composeEngine)
}
val isDarkTheme = when (selectedThemeMode) {
"1" -> false
"2" -> true
@@ -169,6 +176,20 @@ fun ThemeConfigScreen(
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)) {
if (isMiuixEngine) {
DropdownListSettingItem(
@@ -253,7 +274,9 @@ fun ThemeConfigScreen(
selectedValue = ThemeConfig.composeEngine,
displayEntries = stringArrayResource(R.array.composeEngine),
entryValues = stringArrayResource(R.array.composeEngine_value),
onValueChange = { ThemeConfig.composeEngine = it }
onValueChange = {
ThemeConfig.composeEngine = it
}
)
SliderSettingItem(
title = stringResource(R.string.font_scale),
@@ -531,47 +554,46 @@ fun ThemeConfigScreen(
manageKey?.let { isDark ->
BackgroundImageManageSheet(
show = true,
isDarkTheme = isDark,
onDismissRequest = { manageKey = null }
)
}
if (showColorPicker) {
ColorPickerSheet(
initialColor = primaryColorValue.value,
onDismissRequest = { showColorPicker = false },
onColorSelected = { color ->
primaryColorValue.value = color
ThemeConfig.cPrimary = color
ThemeStore.editTheme(context)
.primaryColor(color)
.apply()
DynamicColors.applyToActivitiesIfAvailable(
context.applicationContext as android.app.Application,
DynamicColorsOptions.Builder()
.setContentBasedSource(context.primaryColor)
.build()
)
}
)
}
if (showLauncherIconPicker) {
LauncherIconPickerSheet(
selectedValue = ThemeConfig.launcherIcon,
onDismissRequest = { showLauncherIconPicker = false },
onValueChange = {
ThemeConfig.launcherIcon = it
LauncherIconHelp.changeIcon(it)
}
)
}
ColorPickerSheet(
show = showColorPicker,
initialColor = primaryColorValue.value,
onDismissRequest = { showColorPicker = false },
onColorSelected = { color ->
primaryColorValue.value = color
ThemeConfig.cPrimary = color
ThemeStore.editTheme(context)
.primaryColor(color)
.apply()
DynamicColors.applyToActivitiesIfAvailable(
context.applicationContext as android.app.Application,
DynamicColorsOptions.Builder()
.setContentBasedSource(context.primaryColor)
.build()
)
}
)
if (showThemeListDialog) {
ThemeListDialog(
onDismissRequest = { showThemeListDialog = false }
)
}
LauncherIconPickerSheet(
show = showLauncherIconPicker,
selectedValue = ThemeConfig.launcherIcon,
onDismissRequest = { showLauncherIconPicker = false },
onValueChange = {
ThemeConfig.launcherIcon = it
LauncherIconHelp.changeIcon(it)
}
)
ThemeListDialog(
show = showThemeListDialog,
onDismissRequest = { showThemeListDialog = false }
)
saveThemeKey?.let { key ->
AlertDialog(
@@ -652,7 +674,7 @@ fun ThemeModeSelector(
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.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.text.AppText
import io.legado.app.utils.GSON
import io.legado.app.utils.getClipText
@@ -48,6 +48,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ThemeListDialog(
show: Boolean,
onDismissRequest: () -> Unit
) {
val context = LocalContext.current
@@ -55,7 +56,8 @@ fun ThemeListDialog(
var deleteIndex by remember { mutableStateOf<Int?>(null) }
val themeList = remember(listVersion) { OldThemeConfig.configList.toList() }
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -36,7 +36,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
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 kotlinx.coroutines.launch
@@ -56,7 +56,8 @@ fun DictRuleEditSheet(
var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") }
var showMenu by remember { mutableStateOf(false) }
GlassModalBottomSheet(
AppModalBottomSheet(
show = true,
onDismissRequest = onDismissRequest
) {
Box(
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -44,13 +43,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.base.BaseRuleEvent
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.DraggableSelectionHandler
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.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.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -153,54 +150,49 @@ fun DictRuleScreen(
)
}
if (showExportSheet) {
FilePickerSheet(
onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export),
onSelectSysDir = {
showExportSheet = false
exportDoc.launch("exportDictRule.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
}
FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export),
onSelectSysDir = {
showExportSheet = false
exportDoc.launch("exportDictRule.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
if (showImportSheet) {
FilePickerSheet(
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_dict_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
}
(importState as? BaseImportUiState.Success<DictRule>)?.let { state ->
BatchImportDialog(
title = "导入词典规则",
importState = state,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
itemContent = { rule, _ ->
Column {
AppText(rule.name, style = LegadoTheme.typography.titleMedium)
AppText(rule.urlRule, style = LegadoTheme.typography.bodySmall, maxLines = 1)
}
}
)
}
FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_dict_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
BatchImportDialog(
title = "导入词典规则",
importState = importState,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
itemTitle = { rule -> rule.name },
itemSubtitle = { rule ->
rule.urlRule.takeIf { it.isNotBlank() }
}
)
LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) {
@@ -228,47 +220,46 @@ fun DictRuleScreen(
)
}
if (showEditSheet) {
RuleEditSheet(
rule = editingRule,
title = stringResource(R.string.dict_rule),
label1 = stringResource(R.string.url_rule),
label2 = stringResource(R.string.show_rule),
onDismissRequest = {
showEditSheet = false
editingRule = null
},
onSave = { updatedRule ->
if (editingRule == null) {
viewModel.insert(updatedRule)
} else {
viewModel.update(updatedRule)
}
showEditSheet = false
editingRule = null
},
onCopy = { viewModel.copyRule(it) },
onPaste = { viewModel.pasteRule() },
toFields = { r ->
RuleEditFields(
name = r?.name ?: "",
rule1 = r?.urlRule ?: "",
rule2 = r?.showRule ?: ""
)
},
fromFields = { fields, old ->
old?.copy(
name = fields.name,
urlRule = fields.rule1,
showRule = fields.rule2
) ?: DictRule(
name = fields.name,
urlRule = fields.rule1,
showRule = fields.rule2
)
RuleEditSheet(
show = showEditSheet,
rule = editingRule,
title = stringResource(R.string.dict_rule),
label1 = stringResource(R.string.url_rule),
label2 = stringResource(R.string.show_rule),
onDismissRequest = {
showEditSheet = false
editingRule = null
},
onSave = { updatedRule ->
if (editingRule == null) {
viewModel.insert(updatedRule)
} else {
viewModel.update(updatedRule)
}
)
}
showEditSheet = false
editingRule = null
},
onCopy = { viewModel.copyRule(it) },
onPaste = { viewModel.pasteRule() },
toFields = { r ->
RuleEditFields(
name = r?.name ?: "",
rule1 = r?.urlRule ?: "",
rule2 = r?.showRule ?: ""
)
},
fromFields = { fields, old ->
old?.copy(
name = fields.name,
urlRule = fields.rule1,
showRule = fields.rule2
) ?: DictRule(
name = fields.name,
urlRule = fields.rule1,
showRule = fields.rule2
)
}
)
RuleListScaffold(
title = "字典规则",
@@ -310,7 +301,7 @@ fun DictRuleScreen(
snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_str)) },
text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
@@ -26,6 +26,7 @@ import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem
import androidx.compose.material3.WideNavigationRailValue
@@ -195,7 +196,7 @@ fun MainScreen(
) { dismiss ->
bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
RoundDropdownMenuItem(
text = { AppText(group.groupName) },
text = group.groupName,
onClick = {
coroutineScope.launch {
if (pagerState.currentPage != index) {
@@ -321,7 +322,7 @@ private fun NavigationIcon(
val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon
Box(modifier = modifier) {
if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
BadgedBox(badge = { Badge { AppText(upBooksCount.toString()) } }) {
BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) {
Icon(icon, contentDescription = null)
}
} else {
@@ -31,38 +31,37 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.widget.components.card.GlassCard
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.CompactSliderSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookshelfConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit
) {
val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
AppModalBottomSheet(
title = stringResource(R.string.bookshelf_layout),
show = show,
onDismissRequest = onDismissRequest
) {
GlassCard() {
}
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.animateContentSize()
.padding(horizontal = 16.dp)
.padding(bottom = 32.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(
title = stringResource(R.string.group_style),
selectedValue = BookshelfConfig.bookGroupStyle.toString(),
@@ -40,12 +40,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
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.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -60,13 +57,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
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.menuItem.RoundDropdownMenu
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.startActivity
import io.legado.app.utils.toastOnUi
@@ -237,37 +233,37 @@ fun BookshelfScreen(
topBarActions = { },
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.add_remote_book)) },
text = stringResource(R.string.add_remote_book),
onClick = { context.startActivity<RemoteBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Wifi, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.book_local)) },
text = stringResource(R.string.book_local),
onClick = { context.startActivity<ImportBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Save, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.update_toc)) },
text = stringResource(R.string.update_toc),
onClick = { viewModel.upToc(uiState.items); dismiss() },
leadingIcon = { Icon(Icons.Default.Refresh, null) }
)
RoundDropdownMenuItem(
text = { AppText("布局设置") },
text = "布局设置",
onClick = { showConfigSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.GridView, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.group_manage)) },
text = stringResource(R.string.group_manage),
onClick = { showGroupManageSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Edit, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.add_url)) },
text = stringResource(R.string.add_url),
onClick = { showAddUrlDialog = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Link, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.bookshelf_management)) },
text = stringResource(R.string.bookshelf_management),
onClick = {
val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -279,7 +275,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Settings, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.cache_export)) },
text = stringResource(R.string.cache_export),
onClick = {
val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -291,7 +287,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Download, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.export_bookshelf)) },
text = stringResource(R.string.export_bookshelf),
onClick = {
showExportSheet = true
dismiss()
@@ -299,12 +295,12 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.ImportExport, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_bookshelf)) },
text = stringResource(R.string.import_bookshelf),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.log)) },
text = stringResource(R.string.log),
onClick = {
showLogSheet = true
dismiss()
@@ -326,7 +322,7 @@ fun BookshelfScreen(
uiState.groups.map { it.groupName }
}
AdaptiveTabRow(
AppTabRow(
tabTitles = tabTitles,
selectedTabIndex = selectedTabIndex,
onTabSelected = { index ->
@@ -349,7 +345,7 @@ fun BookshelfScreen(
) { dismiss ->
uiState.groups.forEachIndexed { index, group ->
RoundDropdownMenuItem(
text = { AppText(group.groupName) },
text = group.groupName,
onClick = {
scope.launch { pagerState.animateScrollToPage(index) }
dismiss()
@@ -487,13 +483,15 @@ fun BookshelfScreen(
}
}
if (showConfigSheet) {
BookshelfConfigSheet(onDismissRequest = { showConfigSheet = false })
}
BookshelfConfigSheet(
show = showConfigSheet,
onDismissRequest = { showConfigSheet = false }
)
if (showGroupManageSheet) {
GroupManageSheet(onDismissRequest = { showGroupManageSheet = false })
}
GroupManageSheet(
show = showGroupManageSheet,
onDismissRequest = { showGroupManageSheet = false }
)
if (showAddUrlDialog) {
SourceInputDialog(
@@ -506,40 +504,39 @@ fun BookshelfScreen(
)
}
if (showImportSheet) {
FilePickerSheet(
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_bookshelf),
onSelectSysFile = { types ->
importLauncher.launch(types)
showImportSheet = false
},
onManualInput = {
showAddUrlDialog = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
}
FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_bookshelf),
onSelectSysFile = { types ->
importLauncher.launch(types)
showImportSheet = false
},
onManualInput = {
showAddUrlDialog = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
if (showExportSheet) {
FilePickerSheet(
onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export_bookshelf),
onSelectSysDir = {
showExportSheet = false
exportLauncher.launch("bookshelf.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadBookshelf(uiState.items)
}
)
}
FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export_bookshelf),
onSelectSysDir = {
showExportSheet = false
exportLauncher.launch("bookshelf.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadBookshelf(uiState.items)
}
)
if (showLogSheet) {
AppLogSheet(onDismissRequest = { showLogSheet = false })
}
AppLogSheet(
show = showLogSheet,
onDismissRequest = { showLogSheet = false }
)
if (uiState.isLoading) {
Dialog(onDismissRequest = {}) {
@@ -6,18 +6,13 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
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.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -26,7 +21,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
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.ui.book.group.GroupEditContent
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.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.move
import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -46,6 +39,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GroupManageSheet(
show: Boolean,
onDismissRequest: () -> Unit,
viewModel: GroupViewModel = 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(
targetState = isEditing,
transitionSpec = {
@@ -93,56 +102,28 @@ fun GroupManageSheet(
viewModel = viewModel
)
} else {
Column(
modifier = Modifier
.fillMaxWidth()
LazyColumn(
state = listState,
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
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
items(listData, key = { it.groupId }) { group ->
val manageNameInfo = remember(group) { group.getManageName(context) }
ReorderableSelectionItem(
state = reorderableState,
key = group.groupId,
title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
isEnabled = group.show,
containerColor = MaterialTheme.colorScheme.surface,
onEnabledChange = { isChecked ->
viewModel.upGroup(group.copy(show = isChecked))
},
onClickEdit = {
editingGroup = group
isEditing = true
}
)
IconButton(onClick = {
editingGroup = null
isEditing = true
}) {
Icon(
Icons.Default.Add,
contentDescription = stringResource(R.string.add)
)
}
}
LazyColumn(
state = listState,
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(listData, key = { it.groupId }) { group ->
val manageNameInfo = remember(group) { group.getManageName(context) }
ReorderableSelectionItem(
state = reorderableState,
key = group.groupId,
title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
isEnabled = group.show,
containerColor = MaterialTheme.colorScheme.surface,
onEnabledChange = { isChecked ->
viewModel.upGroup(group.copy(show = isChecked))
},
onClickEdit = {
editingGroup = group
isEditing = true
}
)
}
}
}
}
@@ -153,13 +153,13 @@ fun ExploreScreen(
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { AppText(stringResource(R.string.all)) },
text = stringResource(R.string.all),
onClick = { viewModel.setGroup(""); dismiss() }
)
uiState.groups.forEach { group ->
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { AppText(group) },
text = group,
onClick = { viewModel.setGroup(group); dismiss() }
)
}
@@ -416,29 +416,29 @@ fun ExploreSourceHeader(
PillHeaderDivider(title = item.bookSourceName)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { AppText(stringResource(R.string.to_top)) },
text = stringResource(R.string.to_top),
onClick = { onTop(); showMenu = false }
)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { AppText(stringResource(R.string.edit)) },
text = stringResource(R.string.edit),
onClick = { onEdit(); showMenu = false }
)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Search) },
text = { AppText(stringResource(R.string.search)) },
text = stringResource(R.string.search),
onClick = { onSearch(); showMenu = false }
)
if (item.hasLoginUrl) {
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { AppText(stringResource(R.string.login)) },
text = stringResource(R.string.login),
onClick = { onLogin(); showMenu = false }
)
}
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Refresh) },
text = { AppText(stringResource(R.string.refresh)) },
text = stringResource(R.string.refresh),
onClick = { onRefresh(); showMenu = false }
)
RoundDropdownMenuItem(
@@ -448,12 +448,8 @@ fun ExploreSourceHeader(
tint = MaterialTheme.colorScheme.error
)
},
text = {
AppText(
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
text = stringResource(R.string.delete),
color = LegadoTheme.colorScheme.error,
onClick = { onDelete(); showMenu = false }
)
}
@@ -239,7 +239,7 @@ fun WebServiceSettingBlock(
Row(
modifier = Modifier
.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
) {
SmallTextButton(
@@ -136,12 +136,12 @@ fun RssScreen(
dismiss()
},
leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
text = { AppText("订阅源管理") }
text = "订阅源管理"
)
PillDivider()
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { AppText(stringResource(R.string.all)) },
text = stringResource(R.string.all),
onClick = {
viewModel.setGroup("")
dismiss()
@@ -150,7 +150,7 @@ fun RssScreen(
uiState.groups.forEach { group ->
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { AppText(group) },
text = group,
onClick = {
viewModel.setGroup(group)
dismiss()
@@ -247,7 +247,7 @@ fun RssSourceGridItem(
PillHeaderDivider(title = source.sourceName)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { AppText(stringResource(R.string.to_top)) },
text = stringResource(R.string.to_top),
onClick = {
onTop()
showMenu = false
@@ -255,7 +255,7 @@ fun RssSourceGridItem(
)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { AppText(stringResource(R.string.edit)) },
text = stringResource(R.string.edit),
onClick = {
onEdit()
showMenu = false
@@ -264,7 +264,7 @@ fun RssSourceGridItem(
if (!source.loginUrl.isNullOrBlank()) {
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { AppText(stringResource(R.string.login)) },
text = stringResource(R.string.login),
onClick = {
onLogin()
showMenu = false
@@ -273,7 +273,7 @@ fun RssSourceGridItem(
}
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Close) },
text = { AppText(stringResource(R.string.disable_source)) },
text = stringResource(R.string.disable_source),
onClick = {
onDisable()
showMenu = false
@@ -286,12 +286,8 @@ fun RssSourceGridItem(
tint = MaterialTheme.colorScheme.error
)
},
text = {
AppText(
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
text = stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error,
onClick = {
onDelete()
showMenu = false
@@ -8,7 +8,6 @@ import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
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.FileOpen
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton
@@ -57,7 +55,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.base.BaseRuleEvent
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.DraggableSelectionHandler
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.menuItem.RoundDropdownMenuItem
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 kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@@ -161,56 +158,48 @@ fun ReplaceRuleScreen(
)
}
if (showImportSheet) {
FilePickerSheet(
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_replace_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
}
FilePickerSheet(
show = showImportSheet,
onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_replace_rule),
onSelectSysFile = { types ->
importDoc.launch(types)
showImportSheet = false
},
onManualInput = {
showUrlInput = true
showImportSheet = false
},
allowExtensions = arrayOf("json", "txt")
)
if (showExportSheet) {
FilePickerSheet(
onDismissRequest = { showExportSheet = false },
onSelectSysDir = {
showExportSheet = false
exportDoc.launch("exportReplaceRule.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
}
FilePickerSheet(
show = showExportSheet,
onDismissRequest = { showExportSheet = false },
onSelectSysDir = {
showExportSheet = false
exportDoc.launch("exportReplaceRule.json")
},
onUpload = {
showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
(importState as? BaseImportUiState.Success<ReplaceRule>)?.let { state ->
BatchImportDialog(
title = stringResource(R.string.import_replace_rule),
importState = state,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
topBarActions = {},
itemContent = { rule, _ ->
Column {
AppText(rule.name, style = LegadoTheme.typography.titleMedium)
if (!rule.group.isNullOrBlank()) {
AppText(rule.group!!, style = LegadoTheme.typography.bodySmall)
}
}
}
)
}
BatchImportDialog(
title = stringResource(R.string.import_replace_rule),
importState = importState,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
topBarActions = {},
itemTitle = { rule -> rule.name },
itemSubtitle = { rule ->
rule.group?.takeIf { it.isNotBlank() }
}
)
if (importState is BaseImportUiState.Loading) {
Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() }
@@ -263,14 +252,15 @@ fun ReplaceRuleScreen(
}
}
if (showGroupManageSheet) {
GroupManageBottomSheet(
groups = groups,
onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) }
)
}
GroupManageBottomSheet(
show = showGroupManageSheet,
groups = groups,
onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) }
)
showDeleteRuleDialog?.let { rule ->
AlertDialog(
@@ -343,7 +333,7 @@ fun ReplaceRuleScreen(
},
bottomContent = {
if (tabItems.size > 1) {
AdaptiveTabRow(
AppTabRow(
tabTitles = tabItems,
selectedTabIndex = selectedTabIndex,
onTabSelected = { index ->
@@ -384,29 +374,29 @@ fun ReplaceRuleScreen(
snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_str)) },
text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
RoundDropdownMenuItem(
text = { AppText("分组管理") },
text = "分组管理",
onClick = { showGroupManageSheet = true; dismiss() }
)
RoundDropdownMenuItem(
text = { AppText("帮助") },
text = "帮助",
onClick = { /*TODO*/ dismiss() }
)
PillDivider()
RoundDropdownMenuItem(
text = { AppText("旧的在前") },
text = "旧的在前",
onClick = { viewModel.setSortMode("asc"); dismiss() }
)
RoundDropdownMenuItem(
text = { AppText("新的在前") },
text = "新的在前",
onClick = { viewModel.setSortMode("desc"); dismiss() }
)
RoundDropdownMenuItem(
text = { AppText("名称升序") },
text = "名称升序",
onClick = {
viewModel.setSortMode("name_asc")
dismiss()
@@ -416,7 +406,7 @@ fun ReplaceRuleScreen(
}
)
RoundDropdownMenuItem(
text = { AppText("名称降序") },
text = "名称降序",
onClick = {
viewModel.setSortMode("name_desc")
dismiss()
@@ -473,16 +463,16 @@ fun ReplaceRuleScreen(
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
),
dropdownContent = { dismiss ->
DropdownMenuItem(
text = { AppText("移至顶部") },
RoundDropdownMenuItem(
text = "移至顶部",
onClick = { viewModel.toTop(ui.rule); dismiss() }
)
DropdownMenuItem(
text = { AppText("移至底部") },
RoundDropdownMenuItem(
text = "移至底部",
onClick = { viewModel.toBottom(ui.rule); dismiss() }
)
DropdownMenuItem(
text = { AppText("删除") },
RoundDropdownMenuItem(
text = "删除",
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.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.startActivity
@OptIn(ExperimentalMaterial3Api::class)
@@ -129,11 +126,10 @@ fun RssFavoritesScreen(
topBarActions = {},
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = {
text = stringResource(R.string.all),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
AppText(stringResource(R.string.all))
}
},
onClick = {
@@ -143,15 +139,14 @@ fun RssFavoritesScreen(
)
groups.forEach { group ->
RoundDropdownMenuItem(
text = {
text = group,
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
Icons.AutoMirrored.Outlined.Label,
null,
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.width(12.dp))
AppText(group)
}
},
onClick = {
@@ -162,11 +157,10 @@ fun RssFavoritesScreen(
}
PillDivider()
RoundDropdownMenuItem(
text = {
text = stringResource(R.string.delete_select_group),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
AppText(stringResource(R.string.delete_select_group))
}
},
onClick = {
@@ -175,11 +169,10 @@ fun RssFavoritesScreen(
}
)
RoundDropdownMenuItem(
text = {
text = stringResource(R.string.all),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
AppText(stringResource(R.string.all))
}
},
onClick = {
@@ -248,14 +241,14 @@ fun RssFavoritesScreen(
},
dropdownContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.change_group)) },
text = stringResource(R.string.change_group),
onClick = {
showSetGroupDialog = rssStar
dismiss()
}
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.delete)) },
text = stringResource(R.string.delete),
onClick = {
viewModel.deleteStar(rssStar)
dismiss()
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -41,7 +40,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.base.BaseRuleEvent
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.DraggableSelectionHandler
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.divider.PillDivider
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.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -175,50 +172,41 @@ fun RssSourceScreen(
)
}
if (showGroupManageSheet) {
GroupManageBottomSheet(
groups = groups,
onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) }
)
}
GroupManageBottomSheet(
show = showGroupManageSheet,
groups = groups,
onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) }
)
if (showFilePickerSheet) {
FilePickerSheet(
onDismissRequest = { showFilePickerSheet = false },
onSelectSysDir = {
showFilePickerSheet = false
exportDoc.launch("exportRssSource.json")
},
onUpload = {
showFilePickerSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
}
(importState as? BaseImportUiState.Success<RssSource>)?.let { state ->
BatchImportDialog(
title = stringResource(R.string.import_rss_source),
importState = state,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
itemContent = { source, _ ->
Column {
AppText(source.sourceName, style = LegadoTheme.typography.titleMedium)
AppText(
source.sourceUrl,
style = LegadoTheme.typography.bodySmall,
maxLines = 1
)
}
}
)
}
FilePickerSheet(
show = showFilePickerSheet,
onDismissRequest = { showFilePickerSheet = false },
onSelectSysDir = {
showFilePickerSheet = false
exportDoc.launch("exportRssSource.json")
},
onUpload = {
showFilePickerSheet = false
viewModel.uploadSelectedRules(selectedIds, rules)
},
allowExtensions = arrayOf("json")
)
BatchImportDialog(
title = stringResource(R.string.import_rss_source),
importState = importState,
onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleAll = { viewModel.toggleImportAll(it) },
onConfirm = { viewModel.saveImportedRules() },
itemTitle = { rule -> rule.sourceName },
itemSubtitle = { rule ->
rule.sourceUrl.takeIf { it.isNotBlank() }
}
)
LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) {
@@ -291,11 +279,11 @@ fun RssSourceScreen(
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
onClick = { showGroupManageSheet = true },
text = { AppText("分组管理") },
text = "分组管理",
)
Box {
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_rss_source)) },
text = stringResource(R.string.import_rss_source),
onClick = { showImportMenu = true }
)
RoundDropdownMenu(
@@ -303,7 +291,7 @@ fun RssSourceScreen(
onDismissRequest = { showImportMenu = false }
) {
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_on_line)) },
text = stringResource(R.string.import_on_line),
onClick = {
showImportMenu = false
dismiss()
@@ -311,7 +299,7 @@ fun RssSourceScreen(
}
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_local)) },
text = stringResource(R.string.import_local),
onClick = {
showImportMenu = false
dismiss()
@@ -319,7 +307,7 @@ fun RssSourceScreen(
}
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.import_default_rule)) },
text = stringResource(R.string.import_default_rule),
onClick = {
showImportMenu = false
dismiss()
@@ -330,29 +318,29 @@ fun RssSourceScreen(
}
PillDivider()
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.all)) },
text = stringResource(R.string.all),
onClick = { dismiss(); viewModel.setGroupFilter(null) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.enabled)) },
text = stringResource(R.string.enabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.disabled)) },
text = stringResource(R.string.disabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.need_login)) },
text = stringResource(R.string.need_login),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) }
)
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.no_group)) },
text = stringResource(R.string.no_group),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) }
)
PillDivider()
groups.forEach { group ->
RoundDropdownMenuItem(
text = { AppText(group) },
text = group,
onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$group") }
)
}
@@ -89,7 +89,8 @@ fun RuleSubScreen(
topBarActions = {},
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = {
text = stringResource(R.string.sort),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.size(12.dp))
@@ -185,7 +186,7 @@ fun RuleSubScreen(
},
dropdownContent = { dismiss ->
RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.delete)) },
text = stringResource(R.string.delete),
onClick = {
viewModel.delete(ruleSub)
dismiss()
@@ -11,6 +11,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import io.legado.app.ui.config.themeConfig.ThemeConfig
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.theme.ThemeController
@@ -46,8 +47,15 @@ fun AppTheme(
)
}
val themeColors = remember(colorScheme, darkTheme, seedColor, paletteStyle, colorSchemeMode) {
LegadoThemeColors(
val themeColors = remember(
colorScheme,
darkTheme,
seedColor,
paletteStyle,
colorSchemeMode,
composeEngine
) {
LegadoThemeMode(
colorScheme = colorScheme,
isDark = darkTheme,
seedColor = seedColor,
@@ -61,45 +69,230 @@ fun AppTheme(
CompositionLocalProvider(
LocalLegadoThemeColors provides themeColors
) {
when {
ThemeResolver.isMiuixEngine(themeColors.composeEngine) -> {
val controller = remember(colorSchemeMode, darkTheme) {
ThemeController(
colorSchemeMode = colorSchemeMode,
isDark = darkTheme
if (ThemeResolver.isMiuixEngine(themeColors.composeEngine)) {
val controller = remember(colorSchemeMode, darkTheme) {
ThemeController(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(
controller = controller
) {
val miuixTextStyles = MiuixTheme.textStyles
val mappedTypography = remember(miuixTextStyles) {
miuixStylesToM3Typography(miuixTextStyles)
}
val miuixColorScheme = MiuixTheme.colorScheme
CompositionLocalProvider(
LocalLegadoTypography provides mappedTypography
) {
AppBackground(darkTheme = darkTheme) {
content()
}
}
val mappedColorScheme = remember(miuixColorScheme) {
LegadoColorScheme(
primary = miuixColorScheme.primary,
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(
LocalLegadoTypography provides legadoTypography,
LocalLegadoColorScheme provides mappedColorScheme
) {
AppBackground(darkTheme = darkTheme) { content() }
}
}
} else {
val Typography = Typography()
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography,
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) {
val legadoTypography = remember(Typography) {
LegadoTypography(
headlineLarge = Typography.headlineLarge,
headlineLargeEmphasized = Typography.headlineLargeEmphasized,
headlineMedium = Typography.headlineMedium,
headlineMediumEmphasized = Typography.headlineMediumEmphasized,
headlineSmall = Typography.headlineSmall,
headlineSmallEmphasized = Typography.headlineSmallEmphasized,
else -> {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
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()
}
AppBackground(darkTheme = darkTheme) { content() }
}
}
}
}
}
}
@@ -1,18 +1,18 @@
package io.legado.app.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.Typography
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import com.materialkolor.PaletteStyle
import dev.chrisbanes.haze.HazeState
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
data class LegadoThemeColors(
data class LegadoThemeMode(
val colorScheme: ColorScheme,
val isDark: Boolean,
val seedColor: Color,
@@ -22,30 +22,117 @@ data class LegadoThemeColors(
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 {
LegadoThemeColors(
LegadoThemeMode(
colorScheme = lightColorScheme(),
isDark = false,
seedColor = Color.Unspecified,
paletteStyle = PaletteStyle.TonalSpot,
themeMode = ColorSchemeMode.System,
useDynamicColor = true,
composeEngine = "m3"
composeEngine = "material"
)
}
val LocalLegadoTypography = staticCompositionLocalOf {
Typography()
}
val LocalHazeState = compositionLocalOf<HazeState?> { null }
object LegadoTheme {
val colorScheme: ColorScheme
val colorScheme: LegadoColorScheme
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.colorScheme
get() = LocalLegadoColorScheme.current
val isDark: Boolean
@Composable
@@ -77,7 +164,7 @@ object LegadoTheme {
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.useDynamicColor
val typography: Typography
val typography: LegadoTypography
@Composable
@ReadOnlyComposable
get() = LocalLegadoTypography.current
@@ -66,7 +66,8 @@ object ThemeManager {
}
AppThemeMode.CUSTOM -> {
CustomColorScheme(context.primaryColor, style)
val colorSpec = ThemeResolver.resolveColorSpecFromMaterialVersion(materialVersion)
CustomColorScheme(context.primaryColor, style, colorSpec)
.getColorScheme(darkTheme)
}
@@ -93,4 +94,4 @@ object ThemeManager {
return scheme
}
}
}
@@ -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.material.icons.Icons
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.Icon
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.unit.dp
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
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun CollapsibleHeader(
modifier: Modifier = Modifier,
showIcon: Boolean = true,
isCollapsed: Boolean,
onToggle: () -> Unit,
title: String,
subtitle: String? = null,
titleContent: (@Composable ColumnScope.() -> Unit)? = null
) {
Card(
GlassCard(
modifier = modifier
.fillMaxWidth()
.padding(start = 16.dp, top = 4.dp, end = 16.dp),
shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer
),
containerColor = LegadoTheme.colorScheme.surfaceContainer,
onClick = onToggle
) {
Row(
@@ -59,7 +57,7 @@ fun CollapsibleHeader(
text = title,
style = LegadoTheme.typography.bodySmallEmphasized.copy(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
color = LegadoTheme.colorScheme.primary
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -68,7 +66,7 @@ fun CollapsibleHeader(
AppText(
text = it,
style = LegadoTheme.typography.labelSmallEmphasized.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant
color = LegadoTheme.colorScheme.onSurfaceVariant
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -82,12 +80,14 @@ fun CollapsibleHeader(
label = "arrowRotation"
)
Icon(
imageVector = Icons.Default.ExpandMore,
contentDescription = if (isCollapsed) "展开" else "折叠",
modifier = Modifier.rotate(rotation),
tint = MaterialTheme.colorScheme.primary
)
if (showIcon) {
Icon(
imageVector = Icons.Default.ExpandMore,
contentDescription = if (isCollapsed) "展开" else "折叠",
modifier = Modifier.rotate(rotation),
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.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.theme.LegadoTheme
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.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.NavigationBar as MiuixNavigationBar
@@ -85,7 +85,7 @@ fun RowScope.AppNavigationBarItem(
icon = m3Icon,
colors = NavigationBarItemDefaults.colors(indicatorColor = m3IndicatorColor),
label = if (m3ShowLabel) {
{ AppText(labelString) }
{ Text(labelString) }
} else null,
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.sp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -1,12 +1,10 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.input.rememberTextFieldState
@@ -25,47 +23,36 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
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.text.AppText
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GroupManageBottomSheet(
show: Boolean,
groups: List<String>,
onDismissRequest: () -> Unit,
onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit,
onDeleteGroup: (group: String) -> Unit
) {
GlassModalBottomSheet(
onDismissRequest = onDismissRequest
AppModalBottomSheet(
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)) {
items(groups, key = { it }) { group ->
GroupItem(
group = group,
onUpdateGroup = onUpdateGroup,
onDeleteGroup = onDeleteGroup
)
}
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(groups, key = { it }) { group ->
GroupItem(
group = group,
onUpdateGroup = onUpdateGroup,
onDeleteGroup = onDeleteGroup
)
}
}
}
@@ -1,32 +1,14 @@
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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.clearText
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.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -37,14 +19,15 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -53,10 +36,10 @@ fun SearchBarSection(
onQueryChange: (String) -> Unit,
placeholder: String = "搜索...",
leadingIcon: @Composable (() -> Unit)? = {
SmallIconButton(
icon = Icons.Default.Search,
contentDescription = null,
onClick = {})
AppIcon(
imageVector = AppIcons.Search,
contentDescription = null
)
},
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow,
scrollState: LazyListState? = null,
@@ -65,7 +48,6 @@ fun SearchBarSection(
dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null
) {
var showMenu by remember { mutableStateOf(false) }
val textFieldState = rememberTextFieldState(initialText = query)
LaunchedEffect(query) {
@@ -91,79 +73,19 @@ fun SearchBarSection(
}
}
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
shape = RoundedCornerShape(32.dp),
color = backgroundColor
) {
TextField(
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val searchTextField = @Composable {
AppTextField(
state = textFieldState,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 48.dp)
.padding(horizontal = 4.dp),
.then(
if (isMiuix) Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
else Modifier
),
placeholder = { AppText(placeholder) },
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,
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
@@ -171,13 +93,21 @@ fun SearchBarSection(
disabledContainerColor = Color.Transparent,
focusedIndicatorColor = 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.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -25,13 +22,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.widget.components.button.PrimaryButton
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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookmarkEditSheet(
show: Boolean,
bookmark: Bookmark,
onDismiss: () -> Unit,
onSave: (Bookmark) -> Unit,
@@ -42,21 +41,16 @@ fun BookmarkEditSheet(
var bookText by remember { mutableStateOf(bookmark.bookText) }
var content by remember { mutableStateOf(bookmark.content) }
GlassModalBottomSheet(
onDismissRequest = onDismiss
AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss,
title = bookmark.chapterName,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.navigationBarsPadding()
) {
AppText(
text = bookmark.chapterName,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField(
value = bookText,
onValueChange = { bookText = it },
@@ -82,15 +76,13 @@ fun BookmarkEditSheet(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedButton(
SecondaryButton(
onClick = { showDeleteConfirmDialog = true },
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
modifier = Modifier.weight(1f)
) {
AppText("删除")
}
modifier = Modifier.weight(1f),
text = "删除"
)
Button(
PrimaryButton(
onClick = {
val newBookmark = bookmark.apply {
this.bookText = bookText
@@ -98,10 +90,9 @@ fun BookmarkEditSheet(
}
onSave(newBookmark)
},
modifier = Modifier.weight(1f)
) {
AppText("保存")
}
modifier = Modifier.weight(1f),
text = "保存"
)
}
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.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -31,7 +30,7 @@ fun BookmarkItem(
onLongClick: () -> Unit
) {
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,
label = "BgColor"
)
@@ -53,7 +52,7 @@ fun BookmarkItem(
AppText(
text = bookmark.chapterName,
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()) {
@@ -63,7 +62,7 @@ fun BookmarkItem(
style = LegadoTheme.typography.bodyMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurfaceVariant
color = LegadoTheme.colorScheme.onSurfaceVariant
)
}
@@ -72,7 +71,7 @@ fun BookmarkItem(
AppText(
text = bookmark.content,
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 io.legado.app.R
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.extended.Back
import top.yukonga.miuix.kmp.theme.LocalContentColor
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@Composable
fun MiuixButton(
fun MiuixIconButton(
modifier: Modifier = Modifier,
imageVector: ImageVector = MiuixIcons.Regular.Back,
tint: Color = LocalContentColor.current,
contentDescription: String? = stringResource(id = R.string.back),
onClick: () -> Unit,
) {
IconButton(
MiuixIconButton(
onClick = onClick,
modifier = modifier
) {
@@ -44,7 +44,7 @@ fun MiuixToggleButton(
uncheckedTint: Color = LocalContentColor.current,
contentDescription: String? = null,
) {
IconButton(
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier
) {
@@ -1,6 +1,8 @@
package io.legado.app.ui.widget.components.button
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.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -80,8 +82,24 @@ fun SmallIconButton(
fun SmallOutlinedIconButton(
onClick: () -> Unit,
icon: ImageVector,
modifier: Modifier = Modifier,
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) {
OutlinedIconButton(
onClick = onClick,
@@ -102,6 +120,7 @@ fun SmallOutlinedIconButton(
)
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -109,27 +128,42 @@ fun SmallOutlinedIconButton(
fun SmallTonalIconButton(
onClick: () -> Unit,
icon: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null
) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
FilledTonalIconButton(
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) {
MiuixIconButton(
onClick = onClick,
modifier = Modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.filledTonalIconButtonColors()
modifier = modifier.size(32.dp),
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
Icon(
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(
IconButtonDefaults.extraSmallIconSize
)
modifier = Modifier.size(18.dp)
)
}
} else {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
FilledTonalIconButton(
onClick = onClick,
modifier = modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.filledTonalIconButtonColors()
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize)
)
}
}
}
}
@@ -142,35 +176,67 @@ fun SmallOutlinedIconToggleButton(
modifier: Modifier = Modifier,
contentDescription: String? = null
) {
val defaultShape = IconButtonDefaults.extraSmallRoundShape
val pressedShape = IconButtonDefaults.extraSmallPressedShape
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
val toggleShapes = remember(defaultShape, checkedShape) {
IconToggleButtonShapes(
shape = defaultShape,
pressedShape = pressedShape,
checkedShape = checkedShape
if (isMiuix) {
val containerColor by animateColorAsState(
targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
animationSpec = tween(150),
label = "MiuixToggleContainerColor"
)
}
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
OutlinedIconToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shapes = toggleShapes
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
) {
Icon(
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
tint = iconTint,
modifier = Modifier.size(18.dp)
)
}
} else {
val defaultShape = IconButtonDefaults.extraSmallRoundShape
val pressedShape = IconButtonDefaults.extraSmallPressedShape
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
val toggleShapes = remember(defaultShape, checkedShape) {
IconToggleButtonShapes(
shape = defaultShape,
pressedShape = pressedShape,
checkedShape = checkedShape
)
}
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
OutlinedIconToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shapes = toggleShapes
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
)
}
}
}
}
@@ -12,7 +12,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.Button as MiuixButton
import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
fun SmallTextButton(
@@ -21,21 +26,33 @@ fun SmallTextButton(
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
TextButton(
onClick = onClick,
modifier = modifier,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
shape = MaterialTheme.shapes.small
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(4.dp))
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixButton(
onClick = onClick,
modifier = modifier,
colors = MiuixButtonDefaults.buttonColors()
) {
MiuixText(
text = text
)
}
} else {
TextButton(
onClick = onClick,
modifier = modifier,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
shape = MaterialTheme.shapes.small
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(4.dp))
AppText(
text = text,
style = LegadoTheme.typography.labelMedium
)
}
}
}
@@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.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.topbar.GlassTopAppBarDefaults
import kotlinx.coroutines.delay
@@ -107,12 +108,12 @@ fun TopBarButton(
fun TopbarNavigationButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
imageVector: ImageVector = AppIcons.Back,
contentDescription: String? = "返回",
style: TopBarButtonVariant = TopBarButtonVariant.Filled
) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixButton(
MiuixIconButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
@@ -138,7 +139,7 @@ fun TopBarActionButton(
) {
val enableProgressive = ThemeConfig.enableProgressiveBlur
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixButton(
MiuixIconButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
@@ -86,7 +86,6 @@ fun SelectionItemCard(
GlassCard(
onClick = onToggleSelection,
modifier = modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.medium,
containerColor = animatedContainerColor,
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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 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.AnimatedTextLine
import io.legado.app.ui.widget.components.AppIcon
import top.yukonga.miuix.kmp.theme.MiuixTheme
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -40,17 +36,8 @@ fun TextCard(
spacing: Dp = 4.dp,
textStyle: TextStyle = LegadoTheme.typography.labelSmallEmphasized
) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
val defaultBackground = if (isMiuix)
MiuixTheme.colorScheme.surfaceContainer
else
MaterialTheme.colorScheme.primaryContainer
val defaultContent = if (isMiuix)
MiuixTheme.colorScheme.onSurface
else
MaterialTheme.colorScheme.primary
val defaultBackground = LegadoTheme.colorScheme.surfaceContainer
val defaultContent = LegadoTheme.colorScheme.onSurface
val finalBackgroundColor = backgroundColor ?: defaultBackground
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.filled.Update
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.card.TextCard
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable
fun BookshelfCover(
@@ -31,16 +27,6 @@ fun BookshelfCover(
sourceOrigin: String? = 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) {
BookCover(
name = name,
@@ -58,8 +44,6 @@ fun BookshelfCover(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(2.dp),
backgroundColor = backgroundColor,
contentColor = contentColor,
cornerRadius = 4.dp,
horizontalPadding = 4.dp,
verticalPadding = 0.dp
@@ -41,12 +41,13 @@ import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
import io.legado.app.R
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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColorPickerSheet(
show: Boolean,
initialColor: Int,
onDismissRequest: () -> Unit,
onColorSelected: (Int) -> Unit
@@ -80,7 +81,7 @@ fun ColorPickerSheet(
)
}
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column(
modifier = Modifier
.fillMaxWidth()
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -14,26 +13,39 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
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
fun PillDivider(
modifier: Modifier = Modifier,
thickness: Dp = 2.dp,
widthFraction: Float = 0.2f,
color: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
color: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
) {
Box(
modifier = modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
contentAlignment = Alignment.Center
) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
Box(
modifier = Modifier
.fillMaxWidth(widthFraction)
.height(thickness)
.clip(CircleShape)
.background(color)
.fillMaxWidth()
.height(4.dp)
.background(MiuixTheme.colorScheme.secondaryContainer)
)
} else {
Box(
modifier = modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.fillMaxWidth(widthFraction)
.height(thickness)
.clip(CircleShape)
.background(color)
)
}
}
}
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -24,8 +23,8 @@ fun PillHeaderDivider(
title: String,
modifier: Modifier = Modifier,
dotSize: Dp = 2.dp,
dotColor: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
titleColor: Color = MaterialTheme.colorScheme.primary
dotColor: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
titleColor: Color = LegadoTheme.colorScheme.primary
) {
Row(
modifier = modifier
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
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.FolderOpen
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
@@ -28,7 +26,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.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
enum class FilePickerSheetMode {
@@ -38,6 +37,7 @@ enum class FilePickerSheetMode {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilePickerSheet(
show: Boolean,
onDismissRequest: () -> Unit,
title: String = stringResource(R.string.select_operation),
onSelectSysDir: (() -> Unit)? = null,
@@ -47,22 +47,17 @@ fun FilePickerSheet(
onUpload: (() -> Unit)? = null,
allowExtensions: Array<String>? = null,
) {
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
title = title,
onDismissRequest = onDismissRequest,
containerColor = { it.surface }
containerColor = LegadoTheme.colorScheme.surface
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
AppText(
text = title,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 20.dp)
)
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -127,7 +122,7 @@ private fun RowScope.FilePickerOptionCard(
.weight(1f)
.height(100.dp),
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceContainerLow,
color = LegadoTheme.colorScheme.surfaceContainerLow,
tonalElevation = 2.dp
) {
Column(
@@ -135,11 +130,11 @@ private fun RowScope.FilePickerOptionCard(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
AppIcon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary
tint = LegadoTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
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
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.Column
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.AssistChip
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
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.OutlinedButton
import androidx.compose.material3.OutlinedTextField
@@ -44,7 +34,6 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -59,9 +48,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AnimatedText
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 kotlinx.coroutines.launch
@@ -116,31 +107,42 @@ fun SourceInputDialog(
)
}
//TODO: 动画
@SuppressLint("ConfigurationScreenWidthHeight")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T> BatchImportDialog(
title: String,
importState: BaseImportUiState.Success<T>,
importState: BaseImportUiState<T>,
onDismissRequest: () -> Unit,
onConfirm: (List<T>) -> Unit, // 返回选中的原始数据列表
onConfirm: (List<T>) -> Unit,
onToggleItem: (index: Int) -> Unit,
onToggleAll: (isSelected: Boolean) -> Unit,
onItemInfoClick: (index: Int) -> Unit = {},
// 插槽:允许调用方自定义顶部菜单
topBarActions: @Composable RowScope.() -> Unit = {},
// 插槽:自定义每一行的显示内容
itemContent: @Composable (data: T, status: ImportStatus) -> Unit
itemTitle: (data: T) -> String,
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 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,
containerColor = { it.surfaceContainer }
containerColor = LegadoTheme.colorScheme.surfaceContainer
) {
AppScaffold(
modifier = Modifier
@@ -171,44 +173,34 @@ fun <T> BatchImportDialog(
ImportBottomBar(
selectedCount = selectedCount,
totalCount = totalCount,
onToggleSelectAll = { isAll -> onToggleAll(isAll) },
onToggleSelectAll = onToggleAll,
onConfirm = {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
val selectedData =
importState.items.filter { it.isSelected }.map { it.data }
onConfirm(selectedData)
}
}
val selectedData =
currentState.items.filter { it.isSelected }.map { it.data }
onConfirm(selectedData)
},
onCancel = {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
onDismissRequest()
}
}
}
onCancel = onDismissRequest
)
}
) { padding ->
LazyColumn(
modifier = Modifier
.padding(padding),
modifier = Modifier.padding(padding),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 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(
title = itemTitle(itemWrapper.data),
subtitle = itemSubtitle(itemWrapper.data),
isSelected = itemWrapper.isSelected,
status = itemWrapper.status,
onClick = { onToggleItem(index) },
onInfoClick = {
scope.launch {
snackbarHostState.showSnackbar("其实还没写桀桀桀")
}
scope.launch { snackbarHostState.showSnackbar("其实还没写桀桀桀") }
onItemInfoClick(index)
},
content = { itemContent(itemWrapper.data, itemWrapper.status) }
}
)
}
}
@@ -216,74 +208,47 @@ fun <T> BatchImportDialog(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ImportItemRow(
fun ImportItemRow(
title: String,
subtitle: String? = null,
isSelected: Boolean,
status: ImportStatus,
onClick: () -> Unit,
onInfoClick: () -> Unit,
content: @Composable () -> Unit
onInfoClick: () -> Unit
) {
val containerColor by animateColorAsState(
targetValue = if (isSelected)
MaterialTheme.colorScheme.secondaryContainer
else
MaterialTheme.colorScheme.surface,
animationSpec = tween(
durationMillis = 200,
easing = FastOutSlowInEasing
),
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(
text = when (status) {
ImportStatus.New -> "新增"
ImportStatus.Update -> "更新"
ImportStatus.Existing -> "已有"
ImportStatus.Error -> "错误"
},
style = LegadoTheme.typography.labelMedium,
color = when (status) {
ImportStatus.New -> MaterialTheme.colorScheme.primary
ImportStatus.Update -> MaterialTheme.colorScheme.secondary
ImportStatus.Error -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.outline
}
)
},
trailingContent = {
IconButton(onClick = onInfoClick) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "详情"
)
}
},
colors = ListItemDefaults.colors(
containerColor = Color.Transparent
SelectionItemCard(
title = title,
subtitle = subtitle,
isSelected = isSelected,
inSelectionMode = true,
onToggleSelection = onClick,
modifier = Modifier.padding(vertical = 4.dp),
trailingAction = {
AppText(
text = when (status) {
ImportStatus.New -> "新增"
ImportStatus.Update -> "更新"
ImportStatus.Existing -> "已有"
ImportStatus.Error -> "错误"
},
style = LegadoTheme.typography.labelMedium,
color = when (status) {
ImportStatus.New -> MaterialTheme.colorScheme.primary
ImportStatus.Update -> MaterialTheme.colorScheme.secondary
ImportStatus.Error -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.outline
},
modifier = Modifier.padding(end = 4.dp)
)
)
}
SmallIconButton(
onClick = onInfoClick,
icon = Icons.Default.Info,
contentDescription = "详情"
)
}
)
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -46,6 +46,7 @@ import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastLastOrNull
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 kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -67,7 +68,7 @@ fun VerticalFastScroller(
listState: LazyListState,
modifier: Modifier = Modifier,
thumbAllowed: () -> Boolean = { true },
thumbColor: Color = MaterialTheme.colorScheme.primary,
thumbColor: Color = LegadoTheme.colorScheme.primary,
topContentPadding: Dp = Dp.Hairline,
bottomContentPadding: 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.Column
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.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
@@ -15,8 +17,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
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 top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.extra.SuperListPopup
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -24,31 +29,50 @@ fun RoundDropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
// M3 专属视觉参数,Miuix 模式下会优雅降级(忽略)
shape: Shape = MaterialTheme.shapes.medium,
shadowElevation: Dp = 4.dp,
verticalSpacing: Dp = 8.dp,
content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit
) {
val colorScheme = rememberOpaqueColorScheme()
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier,
shape = shape,
shadowElevation = shadowElevation,
containerColor = colorScheme.surfaceContainerLow
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
if (isMiuix) {
SuperListPopup(
show = expanded,
onDismissRequest = onDismissRequest,
popupModifier = modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
ListPopupColumn {
Column() {
Spacer(Modifier.height(12.dp))
content(onDismissRequest)
Spacer(Modifier.height(12.dp))
}
}
}
} else {
val colorScheme = rememberOpaqueColorScheme()
DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier,
shape = shape,
shadowElevation = shadowElevation,
containerColor = colorScheme.surfaceContainerLow
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) {
content(onDismissRequest)
Column(
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
) {
content(onDismissRequest)
}
}
}
}
@@ -1,6 +1,8 @@
package io.legado.app.ui.widget.components.menuItem
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -11,54 +13,114 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
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.widget.components.icon.AppIcon
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable
fun RoundDropdownMenuItem(
text: @Composable () -> Unit,
text: String,
color: Color = LegadoTheme.colorScheme.surface,
onClick: () -> Unit,
modifier: Modifier = Modifier,
isSelected: Boolean = false,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
interactionSource: MutableInteractionSource? = null,
) {
val colorScheme = rememberOpaqueColorScheme()
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val interaction = interactionSource ?: remember { MutableInteractionSource() }
val contentColor =
if (enabled) MaterialTheme.colorScheme.onSurface
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
if (isMiuix) {
val backgroundColor = if (isSelected) {
LegadoTheme.colorScheme.primaryContainer
} else {
Color.Transparent
}
Surface(
onClick = onClick,
modifier = modifier
.padding(horizontal = 8.dp)
.fillMaxWidth(),
enabled = enabled,
shape = MaterialTheme.shapes.small,
color = colorScheme.surface,
interactionSource = interaction
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides LegadoTheme.typography.labelLarge
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(
onClick = onClick,
modifier = modifier
.padding(horizontal = 8.dp)
.fillMaxWidth(),
enabled = enabled,
shape = MaterialTheme.shapes.small,
color = colorScheme.surface,
interactionSource = interaction
) {
Row(
modifier = Modifier
@@ -67,17 +129,20 @@ fun RoundDropdownMenuItem(
.widthIn(min = 120.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (leadingIcon != null) {
leadingIcon()
Spacer(Modifier.width(8.dp))
Spacer(Modifier.width(12.dp))
}
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.CenterStart
) {
text()
Text(
modifier = Modifier.widthIn(max = 200.dp),
text = text,
style = LegadoTheme.typography.bodyMediumEmphasized
)
}
if (trailingIcon != null) {
@@ -94,9 +159,9 @@ fun MenuItemIcon(
imageVector: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null,
tint: Color = LocalContentColor.current
tint: Color = Color.Unspecified
) {
Icon(
AppIcon(
imageVector = imageVector,
contentDescription = contentDescription,
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.unit.dp
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 kotlinx.coroutines.launch
@@ -52,6 +52,7 @@ data class RuleEditFields(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T> RuleEditSheet(
show: Boolean,
rule: T?,
title: String,
label1: String,
@@ -74,7 +75,8 @@ fun <T> RuleEditSheet(
fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Box(modifier = Modifier.fillMaxWidth()) {
@@ -1,13 +1,12 @@
package io.legado.app.ui.widget.components.settingItem
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.Remove
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -20,17 +19,22 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
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.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)
@Composable
@@ -41,49 +45,73 @@ fun CompactDropdownSettingItem(
entryValues: Array<String>,
description: String? = null,
imageVector: ImageVector? = null,
color: Color? = MaterialTheme.colorScheme.surface,
color: Color? = LegadoTheme.colorScheme.surface,
shape: Shape = MaterialTheme.shapes.small,
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()
SettingItem(
title = title,
description = description,
imageVector = imageVector,
color = color,
shape = shape,
trailingContent = {
TextCard(
cornerRadius = 8.dp,
horizontalPadding = 8.dp,
verticalPadding = 4.dp,
text = currentEntry,
backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
contentColor = MaterialTheme.colorScheme.onSurface
)
},
dropdownMenu = { onDismiss ->
displayEntries.forEachIndexed { index, display ->
RoundDropdownMenuItem(
text = { AppText(display) },
onClick = {
onValueChange(entryValues[index])
onDismiss()
},
trailingIcon = if (selectedValue == entryValues[index]) {
{
Icon(
Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
}
} else null
)
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(
title = title,
description = description,
imageVector = imageVector,
color = color,
shape = shape,
trailingContent = {
TextCard(
cornerRadius = 8.dp,
horizontalPadding = 8.dp,
verticalPadding = 4.dp,
text = currentEntry,
backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
contentColor = MaterialTheme.colorScheme.onSurface
)
},
dropdownMenu = { onDismiss ->
displayEntries.forEachIndexed { index, display ->
RoundDropdownMenuItem(
text = display,
onClick = {
onValueChange(entryValues[index])
onDismiss()
},
trailingIcon = if (selectedValue == entryValues[index]) {
{
Icon(
Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
}
} else null
)
}
}
)
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -104,59 +132,78 @@ fun CompactSliderSettingItem(
var sliderValue by remember(value) { mutableFloatStateOf(value) }
var displayValue by remember(value) { mutableFloatStateOf(value) }
SettingItem(
title = title,
description = description,
imageVector = imageVector,
color = color,
shape = shape,
expanded = expanded,
onExpandChange = { expanded = it },
trailingContent = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
SmallOutlinedIconButton(
onClick = {
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
onValueChange(newValue)
},
icon = Icons.Default.Remove
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(
title = title,
description = description,
imageVector = imageVector,
color = color,
shape = shape,
expanded = expanded,
onExpandChange = { expanded = it },
trailingContent = {
ValueStepper(
value = value,
displayValue = displayValue,
valueRange = valueRange,
onValueChange = onValueChange
)
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)
},
expandContent = {
Slider(
value = sliderValue,
onValueChange = {
sliderValue = it
displayValue = it
},
icon = Icons.Default.Add
onValueChangeFinished = {
onValueChange(sliderValue)
},
valueRange = valueRange,
steps = steps,
modifier = Modifier.fillMaxWidth()
)
}
},
expandContent = {
Slider(
value = sliderValue,
onValueChange = {
sliderValue = it
displayValue = it
},
onValueChangeFinished = {
onValueChange(sliderValue)
},
valueRange = valueRange,
steps = steps,
modifier = Modifier.fillMaxWidth()
)
}
)
)
}
LaunchedEffect(value) {
if (!expanded) {
@@ -177,20 +224,30 @@ fun CompactSwitchSettingItem(
enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit
) {
SettingItem(
title = title,
description = description,
imageVector = imageVector,
color = color,
shape = shape,
onClick = { if (enabled) onCheckedChange(!checked) },
trailingContent = {
Switch(
modifier = Modifier.scale(0.8f),
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled
)
}
)
if (ThemeResolver.isMiuixEngine(composeEngine)) {
SuperSwitch(
title = title,
summary = description,
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled,
)
} else {
SettingItem(
title = title,
description = description,
imageVector = imageVector,
color = color,
shape = shape,
onClick = { if (enabled) onCheckedChange(!checked) },
trailingContent = {
Switch(
modifier = Modifier.scale(0.8f),
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled
)
}
)
}
}
@@ -11,7 +11,6 @@ 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.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.extra.SuperSpinner
@@ -64,7 +63,7 @@ fun DropdownListSettingItem(
dropdownMenu = { onDismiss ->
displayEntries.forEachIndexed { index, display ->
RoundDropdownMenuItem(
text = { AppText(display) },
text = display,
onClick = {
onValueChange(entryValues[index])
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.ThemeResolver
import io.legado.app.ui.widget.components.AdaptiveSwitch
import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.basic.Switch
import top.yukonga.miuix.kmp.extra.SuperSwitch
@Composable
@@ -23,22 +22,12 @@ fun SwitchSettingItem(
val composeEngine = LegadoTheme.composeEngine
if (ThemeResolver.isMiuixEngine(composeEngine)) {
val toggleAction = {
if (enabled) onCheckedChange(!checked)
}
BasicComponent(
SuperSwitch(
title = title,
summary = description,
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled,
onClick = toggleAction,
endActions = {
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled
)
}
)
} else {
SettingItem(
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -11,12 +12,11 @@ import androidx.compose.ui.text.style.TextOverflow
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.widget.components.text.AppText
import top.yukonga.miuix.kmp.basic.TabRowDefaults
import top.yukonga.miuix.kmp.basic.TabRowWithContour
@Composable
fun AdaptiveTabRow(
fun AppTabRow(
tabTitles: List<String>,
selectedTabIndex: Int,
onTabSelected: (Int) -> Unit,
@@ -48,7 +48,7 @@ fun AdaptiveTabRow(
selected = selectedTabIndex == index,
onClick = { onTabSelected(index) },
text = {
AppText(
Text(
text = title,
maxLines = 1,
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.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.TextOverflow
import androidx.compose.ui.unit.TextUnit
import io.legado.app.ui.widget.components.text.AppText
@Composable
fun AnimatedText(
@@ -1,11 +1,11 @@
package io.legado.app.ui.widget.components.text
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
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.TextOverflow
import androidx.compose.ui.unit.TextUnit
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Text as MiuixText
import io.legado.app.ui.theme.LegadoTheme
@Composable
fun AppText(
@@ -41,53 +38,41 @@ fun AppText(
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle? = null,
) {
val baseStyle = style ?: LegadoTheme.typography.bodyMedium
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixText(
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 ?: 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
)
// 2. 获取默认文本色:直接拿你自己封装的 LegadoTheme.colorScheme.onSurface
val defaultTextColor = LegadoTheme.colorScheme.onSurface
// 3. 颜色降级逻辑:传入的 color -> style 中的 color -> 主题默认色
val finalTextColor = color.takeOrElse {
baseStyle.color.takeOrElse { defaultTextColor }
}
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
fun AppText(
text: AnnotatedString,
text: AnnotatedString, // 接收 AnnotatedString
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
@@ -105,46 +90,36 @@ fun AppText(
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle? = null,
) {
// 1. 获取基础样式:如果外部没传 style,直接拿你自己封装的 LegadoTheme.typography.bodyMedium
// 这完美避开了 M3 的 LocalTextStyle,且自动适配 Miuix/M3 引擎!
val baseStyle = style ?: LegadoTheme.typography.bodyMedium
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixText(
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 ?: 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
)
// 2. 获取默认文本色:直接拿你自己封装的 LegadoTheme.colorScheme.onSurface
val defaultTextColor = LegadoTheme.colorScheme.onSurface
// 3. 颜色降级逻辑:传入的 color -> style 中的 color -> 主题默认色
val finalTextColor = color.takeOrElse {
baseStyle.color.takeOrElse { defaultTextColor }
}
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
)
}
@@ -10,12 +10,9 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
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.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -23,12 +20,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
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.button.TopBarActionButton
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.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3Api::class)
@@ -70,7 +68,7 @@ fun <T> DynamicTopAppBar(
if (isSelecting || onBackClick != null) {
TopbarNavigationButton(
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 "返回"
)
}
@@ -113,7 +111,6 @@ fun <T> DynamicTopAppBar(
query = state.searchKey,
onQueryChange = onSearchQueryChange,
placeholder = searchPlaceholder,
leadingIcon = { Icon(searchLeadingIcon, null) },
trailingIcon = searchTrailingIcon,
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.ThemeResolver
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.text.AdaptiveAnimatedText
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar