Merge branch 'miuix-dev'

# Conflicts:
#	app/version.properties
This commit is contained in:
HapeLee
2026-04-03 01:10:47 +08:00
121 changed files with 5128 additions and 2921 deletions
+5
View File
@@ -285,4 +285,9 @@ dependencies {
implementation(libs.material.kolor)
implementation(libs.haze.core)
implementation(libs.haze.materials)
implementation(libs.miuix)
implementation(libs.miuix.icons)
implementation(libs.capsule)
implementation(libs.backdrop)
implementation(libs.timber)
}
@@ -82,6 +82,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -113,6 +114,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -141,6 +143,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -169,6 +172,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -202,6 +206,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -236,6 +241,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -265,6 +271,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -293,6 +300,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -321,6 +329,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -350,6 +359,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -378,6 +388,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -406,6 +417,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -434,6 +446,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -462,6 +475,7 @@ interface BookDao {
durChapterPos,
latestChapterTitle,
latestChapterTime,
lastCheckCount,
totalChapterNum,
durChapterIndex,
type,
@@ -15,9 +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
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -31,13 +28,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.legado.app.R
import io.legado.app.constant.AppConst.appInfo
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.settingItem.SettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -59,7 +58,7 @@ fun AboutScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.about)) },
title = stringResource(R.string.about),
navigationIcon = {
TopbarNavigationButton(onClick = onBack)
},
@@ -81,9 +80,9 @@ fun AboutScreen(
.width(160.dp)
.align(Alignment.CenterHorizontally)
)
Text(
AppText(
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.bodyLarge,
style = LegadoTheme.typography.bodyLarge,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
@@ -91,16 +90,14 @@ fun AboutScreen(
.wrapContentWidth(Alignment.CenterHorizontally))
TextCard(
text = versionName,
backgroundColor = colorScheme.tertiaryContainer,
contentColor = colorScheme.onTertiaryContainer,
cornerRadius = 8.dp,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(vertical = 4.dp))
Text(
AppText(
text = stringResource(R.string.about_description),
style = MaterialTheme.typography.bodyLarge,
style = LegadoTheme.typography.bodyLarge,
fontSize = 12.sp,
modifier = Modifier
.fillMaxWidth()
@@ -18,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -31,19 +30,23 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.theme.LegadoTheme
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
@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(
@@ -52,9 +55,9 @@ fun AppLogSheet(
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
AppText(
text = stringResource(R.string.log),
style = MaterialTheme.typography.titleLarge,
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.weight(1f)
)
IconButton(onClick = {
@@ -82,17 +85,17 @@ fun AppLogSheet(
showStackTrace?.let { stackTrace ->
AlertDialog(
onDismissRequest = { showStackTrace = null },
title = { Text("Log") },
title = { AppText("Log") },
text = {
SelectionContainer {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(text = stackTrace, style = MaterialTheme.typography.bodySmall)
AppText(text = stackTrace, style = LegadoTheme.typography.bodySmall)
}
}
},
confirmButton = {
TextButton(onClick = { showStackTrace = null }) {
Text(stringResource(android.R.string.ok))
AppText(stringResource(android.R.string.ok))
}
}
)
@@ -110,14 +113,14 @@ private fun LogItem(
.clickable(onClick = onClick)
.padding(16.dp)
) {
Text(
AppText(
text = LogUtils.logTimeFormat.format(Date(item.first)),
style = MaterialTheme.typography.labelMedium,
style = LegadoTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline
)
Text(
AppText(
text = item.second,
style = MaterialTheme.typography.bodyMedium,
style = LegadoTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 4.dp)
)
}
@@ -26,7 +26,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -41,8 +40,6 @@ import io.legado.app.data.entities.Bookmark
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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SearchBarSection
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem
@@ -51,6 +48,8 @@ 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.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
@@ -98,11 +97,7 @@ fun AllBookmarkScreen(
topBar = {
Column {
GlassMediumFlexibleTopAppBar(
title = {
Text(
text = "所有书签"
)
},
title = "所有书签",
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBack)
@@ -132,7 +127,7 @@ fun AllBookmarkScreen(
onDismissRequest = { showMenu = false }
) {
RoundDropdownMenuItem(
text = { Text("导出 JSON") },
text = "导出 JSON",
onClick = {
showMenu = false
pendingExportIsMd = false
@@ -140,7 +135,7 @@ fun AllBookmarkScreen(
}
)
RoundDropdownMenuItem(
text = { Text("导出 Markdown") },
text = "导出 Markdown",
onClick = {
showMenu = false
pendingExportIsMd = true
@@ -242,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
}
)
}
}
@@ -52,7 +52,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -79,12 +78,10 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.model.BookShelfState
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource
import io.legado.app.ui.widget.components.AnimatedTextLine
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SearchBarSection
import io.legado.app.ui.widget.components.button.AnimatedTextButton
import io.legado.app.ui.widget.components.button.TopBarActionButton
@@ -93,7 +90,10 @@ 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
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import org.koin.androidx.compose.koinViewModel
@@ -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
) {
Text(
text = "布局列数",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
)
Spacer(modifier = Modifier.width(12.dp))
TextCard(
text = "$gridColumnCount",
textStyle = MaterialTheme.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)
) {
Text("完成")
}
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),
@@ -294,14 +293,7 @@ fun ExploreShowScreen(
modifier = Modifier.responsiveHazeEffect(
state = hazeState
),
title = {
AnimatedTextLine(
selectedTitle ?: title,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
colors = GlassTopAppBarDefaults.glassColors(),
title = selectedTitle ?: title,
navigationIcon = {
TopbarNavigationButton(onClick = onBack)
},
@@ -346,7 +338,7 @@ fun ExploreShowScreen(
onDismissRequest = { showMenu = false }
) {
RoundDropdownMenuItem(
text = { Text("全部显示") },
text = "全部显示",
onClick = {
viewModel.setFilterState(BookFilterState.SHOW_ALL)
showMenu = false
@@ -358,7 +350,7 @@ fun ExploreShowScreen(
)
RoundDropdownMenuItem(
text = { Text("隐藏已在书架的同源书籍") },
text = "隐藏已在书架的同源书籍",
onClick = {
viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
showMenu = false
@@ -370,7 +362,7 @@ fun ExploreShowScreen(
)
RoundDropdownMenuItem(
text = { Text("隐藏已在书架的非同源书籍") },
text = "隐藏已在书架的非同源书籍",
onClick = {
viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
showMenu = false
@@ -382,7 +374,7 @@ fun ExploreShowScreen(
)
RoundDropdownMenuItem(
text = { Text("只显示不在书架的书籍") },
text = "只显示不在书架的书籍",
onClick = {
viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
showMenu = false
@@ -509,7 +501,7 @@ fun KindGridItem(
enabled = isClickable,
selected = isSelected,
label = {
Text(
AppText(
text = kind.title,
fontSize = 12.sp,
modifier = Modifier.fillMaxWidth(),
@@ -576,32 +568,32 @@ fun ExploreBookItem(
.weight(1f)
.align(Alignment.CenterVertically)) {
Text(
AppText(
text = book.name,
style = MaterialTheme.typography.titleSmall,
style = LegadoTheme.typography.titleSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Row {
Text(
AppText(
text = book.author,
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
maxLines = 1
)
val latestChapter = book.latestChapterTitle
if (!latestChapter.isNullOrEmpty()) {
Text(
AppText(
text = "",
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = Color.Gray,
maxLines = 1
)
Text(
AppText(
text = "最新: $latestChapter",
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -612,9 +604,9 @@ fun ExploreBookItem(
val intro = book.intro?.replace("\\s+".toRegex(), "") ?: ""
if (intro.isNotEmpty()) {
Text(
AppText(
text = intro,
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
color = Color.Gray,
maxLines = 2,
minLines = 2,
@@ -654,9 +646,12 @@ fun ExploreBookGridItem(
val content: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) {
{
Text(
AppText(
text = badgeText,
style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold, fontSize = 9.sp)
style = LegadoTheme.typography.labelSmall.copy(
fontWeight = FontWeight.Bold,
fontSize = 9.sp
)
)
}
} else {
@@ -681,9 +676,9 @@ fun ExploreBookGridItem(
Spacer(modifier = Modifier.height(4.dp))
Text(
AppText(
text = book.name,
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
fontWeight = FontWeight.Bold,
maxLines = 2,
overflow = TextOverflow.Ellipsis
@@ -698,10 +693,10 @@ fun TagChip(text: String) {
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
shape = RoundedCornerShape(4.dp)
) {
Text(
AppText(
text = text,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary
)
}
@@ -742,13 +737,13 @@ fun LoadMoreFooter(
},
label = "FooterTextChange"
) { text ->
Text(
AppText(
text = text,
color = when {
errorMsg != null -> Color.Red
else -> Color.Gray
},
style = MaterialTheme.typography.bodySmall
style = LegadoTheme.typography.bodySmall
)
}
@@ -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
@@ -18,11 +17,9 @@ import androidx.compose.material.icons.filled.Restore
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -39,9 +36,10 @@ import io.legado.app.R
import io.legado.app.data.entities.BookGroup
import io.legado.app.lib.dialogs.alert
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
import io.legado.app.utils.FileUtils
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.SelectImageContract
@@ -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,
) {
Text(
text = stringResource(R.string.group_edit),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top,
@@ -138,7 +129,7 @@ fun GroupEditContent(
OutlinedTextField(
value = groupName,
onValueChange = { groupName = it },
label = { Text(stringResource(R.string.group_name)) },
label = { AppText(stringResource(R.string.group_name)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
@@ -206,7 +197,7 @@ fun GroupEditContent(
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
Button(onClick = {
if (groupName.isEmpty()) {
@@ -235,7 +226,7 @@ fun GroupEditContent(
}
}
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
}
}
@@ -51,7 +51,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -80,6 +79,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.Server
import io.legado.app.help.config.AppConfig
import io.legado.app.model.remote.RemoteBook
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.SelectionActions
@@ -88,7 +88,8 @@ 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
import io.legado.app.utils.FileDoc
@@ -157,19 +158,19 @@ fun RemoteBookScreen(
is RemoteBookDialog.DownloadArchive -> {
AlertDialog(
onDismissRequest = { dialogState = null },
title = { Text(stringResource(R.string.draw)) },
text = { Text(stringResource(R.string.archive_not_found)) },
title = { AppText(stringResource(R.string.draw)) },
text = { AppText(stringResource(R.string.archive_not_found)) },
confirmButton = {
OutlinedButton(onClick = {
scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
dialogState = null
}) {
Text(stringResource(android.R.string.ok))
AppText(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { dialogState = null }) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
}
)
@@ -178,19 +179,19 @@ fun RemoteBookScreen(
is RemoteBookDialog.ReImport -> {
AlertDialog(
onDismissRequest = { dialogState = null },
title = { Text("是否重新加入书架?") },
text = { Text("将会覆盖书籍") },
title = { AppText("是否重新加入书架?") },
text = { AppText("将会覆盖书籍") },
confirmButton = {
OutlinedButton(onClick = {
scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
dialogState = null
}) {
Text(stringResource(android.R.string.ok))
AppText(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { dialogState = null }) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
}
)
@@ -198,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 -> {}
}
}
@@ -259,7 +259,7 @@ fun RemoteBookScreen(
},
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { Text("按名称排序") },
text = "按名称排序",
onClick = {
viewModel.toggleSort(RemoteBookSort.Name)
dismiss()
@@ -271,7 +271,7 @@ fun RemoteBookScreen(
}
)
RoundDropdownMenuItem(
text = { Text("按时间排序") },
text = "按时间排序",
onClick = {
viewModel.toggleSort(RemoteBookSort.Default)
dismiss()
@@ -381,9 +381,9 @@ private fun ServersSheetContent(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
AppText(
text = stringResource(R.string.server_config),
style = MaterialTheme.typography.titleLarge
style = LegadoTheme.typography.titleLarge
)
IconButton(onClick = onAdd) {
Icon(Icons.Default.Add, contentDescription = "添加")
@@ -447,9 +447,9 @@ private fun ServerItem(
ListItem(
modifier = Modifier.animateContentSize(),
headlineContent = {
Text(
AppText(
text = name,
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -462,9 +462,9 @@ private fun ServerItem(
},
supportingContent = url?.let {
{
Text(
AppText(
text = it,
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -514,16 +514,16 @@ private fun ServerConfigSheetContent(
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text(
AppText(
text = if (server == null) "添加服务器" else "编辑服务器",
style = MaterialTheme.typography.titleLarge,
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("名称") },
label = { AppText("名称") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
@@ -531,7 +531,7 @@ private fun ServerConfigSheetContent(
OutlinedTextField(
value = url,
onValueChange = { url = it },
label = { Text("URL") },
label = { AppText("URL") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
@@ -539,7 +539,7 @@ private fun ServerConfigSheetContent(
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") },
label = { AppText("用户名") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
@@ -547,7 +547,7 @@ private fun ServerConfigSheetContent(
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
label = { AppText("密码") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
@@ -571,7 +571,7 @@ private fun ServerConfigSheetContent(
onClick = onCancel,
modifier = Modifier.weight(1f)
) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
Button(
onClick = {
@@ -587,7 +587,7 @@ private fun ServerConfigSheetContent(
modifier = Modifier.weight(1f),
enabled = name.isNotBlank() && url.isNotBlank()
) {
Text("保存")
AppText("保存")
}
}
}
@@ -625,9 +625,9 @@ private fun PathNavigationBar(
itemsIndexed(pathNames) { index, name ->
val isLast = index == pathNames.lastIndex
Text(
AppText(
text = name,
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium,
color = if (isLast)
MaterialTheme.colorScheme.primary
@@ -716,9 +716,9 @@ private fun RemoteBookItem(
modifier = Modifier.weight(1f)
) {
Text(
AppText(
text = book.filename.substringBeforeLast("."),
style = MaterialTheme.typography.bodyMedium,
style = LegadoTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -732,7 +732,7 @@ private fun RemoteBookItem(
if (book.contentType.isNotEmpty()) {
TextCard(
text = book.contentType.uppercase(),
textStyle = MaterialTheme.typography.labelSmall,
textStyle = LegadoTheme.typography.labelSmall,
horizontalPadding = 4.dp,
verticalPadding = 2.dp,
cornerRadius = 4.dp,
@@ -743,11 +743,11 @@ private fun RemoteBookItem(
Spacer(modifier = Modifier.width(6.dp))
}
Text(
AppText(
text = "${ConvertUtils.formatFileSize(book.size)}${
AppConst.dateFormat.format(book.lastModify)
}",
style = MaterialTheme.typography.labelMedium,
style = LegadoTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
@@ -30,7 +30,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -47,12 +46,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.button.TopBarButtonVariant
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.utils.SelectImageContract
import io.legado.app.utils.launch
import io.legado.app.utils.showDialogFragment
@@ -71,7 +71,7 @@ fun BookInfoEditScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(text = stringResource(id = R.string.book_info_edit)) },
title = stringResource(id = R.string.book_info_edit),
navigationIcon = {
TopbarNavigationButton(
onClick = onBack,
@@ -191,35 +191,35 @@ fun BookInfoEditContent(
OutlinedTextField(
value = uiState.name,
onValueChange = { viewModel.onNameChange(it) },
label = { Text("书名") },
label = { AppText("书名") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = uiState.author,
onValueChange = { viewModel.onAuthorChange(it) },
label = { Text("作者") },
label = { AppText("作者") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = uiState.coverUrl ?: "",
onValueChange = { viewModel.onCoverUrlChange(it) },
label = { Text("封面链接") },
label = { AppText("封面链接") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = uiState.intro ?: "",
onValueChange = { viewModel.onIntroChange(it) },
label = { Text("简介") },
label = { AppText("简介") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = uiState.remark ?: "",
onValueChange = { viewModel.onRemarkChange(it) },
label = { Text("备注") },
label = { AppText("备注") },
modifier = Modifier.fillMaxWidth()
)
}
@@ -251,7 +251,7 @@ fun BookTypeDropdown(
state = textFieldState,
readOnly = true,
lineLimits = TextFieldLineLimits.SingleLine,
label = { Text("书籍类型") },
label = { AppText("书籍类型") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
@@ -269,7 +269,7 @@ fun BookTypeDropdown(
) {
bookTypes.forEach { option ->
DropdownMenuItem(
text = { Text(option) },
text = { AppText(option) },
onClick = {
onTypeSelected(option)
expanded = false
@@ -32,18 +32,13 @@ 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
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -78,15 +73,15 @@ import androidx.compose.ui.zIndex
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.widget.components.AnimatedTextLine
import io.legado.app.ui.theme.LegadoTheme
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.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
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
@@ -98,9 +93,14 @@ 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
import io.legado.app.utils.StringUtils.formatFriendlyDate
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@@ -148,11 +148,7 @@ fun ReadRecordScreen(
topBar = {
Column {
GlassMediumFlexibleTopAppBar(
title = {
Text(
text = "阅读记录"
)
},
title = "阅读记录",
subtitle = {
val subTitle = when (displayMode) {
DisplayMode.AGGREGATE -> "汇总视图"
@@ -167,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
@@ -181,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
@@ -271,10 +270,10 @@ fun ReadRecordScreen(
onDismissRequest = {
pendingDeleteAction = null
},
title = { Text("确认删除") },
title = { AppText("确认删除") },
text = {
Column {
Text("确定要删除这条记录吗?")
AppText("确定要删除这条记录吗?")
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier
@@ -289,10 +288,10 @@ fun ReadRecordScreen(
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = skipDeleteConfirmTemp, onCheckedChange = null)
Text(
AppText(
text = "不再提示",
modifier = Modifier.padding(start = 8.dp),
style = MaterialTheme.typography.bodyMedium
style = LegadoTheme.typography.bodyMedium
)
}
}
@@ -311,30 +310,29 @@ fun ReadRecordScreen(
TextButton(onClick = {
pendingDeleteAction = null
}) {
Text("取消")
AppText("取消")
}
}
)
}
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
}
)
}
@@ -346,10 +344,10 @@ fun ReadRecordScreen(
AlertDialog(
onDismissRequest = { mergeDialogData = null },
title = { Text("合并阅读记录") },
title = { AppText("合并阅读记录") },
text = {
Column {
Text("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
AppText("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
Spacer(modifier = Modifier.height(8.dp))
candidates.forEach { candidate ->
@@ -367,10 +365,10 @@ fun ReadRecordScreen(
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = isChecked, onCheckedChange = null)
Text(
AppText(
text = "$author${formatDuring(candidate.readTime)}",
modifier = Modifier.padding(start = 8.dp),
style = MaterialTheme.typography.bodyMedium
style = LegadoTheme.typography.bodyMedium
)
}
}
@@ -386,12 +384,12 @@ fun ReadRecordScreen(
mergeDialogData = null
}
) {
Text("合并")
AppText("合并")
}
},
dismissButton = {
TextButton(onClick = { mergeDialogData = null }) {
Text("取消")
AppText("取消")
}
}
)
@@ -686,21 +684,21 @@ fun LatestReadItem(
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
AppText(
text = record.bookName,
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
AppText(
text = record.bookAuthor.ifBlank { "未知作者" },
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(8.dp))
Text(
AppText(
modifier = Modifier
.basicMarquee(
iterations = Int.MAX_VALUE,
@@ -716,7 +714,7 @@ fun LatestReadItem(
append("${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}")
}
},
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -783,9 +781,9 @@ fun TimelineSessionItem(
modifier = Modifier.width(48.dp),
verticalArrangement = Arrangement.Center
) {
Text(
AppText(
text = endTimeText,
style = MaterialTheme.typography.bodySmall
style = LegadoTheme.typography.bodySmall
)
}
@@ -795,23 +793,23 @@ fun TimelineSessionItem(
Cover(coverPath)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
AppText(
text = session.bookName,
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
AppText(
text = session.bookAuthor.ifBlank { "未知作者" },
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(8.dp))
Text(
AppText(
text = chapterTitle.orEmpty(),
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.outline,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -847,21 +845,21 @@ fun ReadRecordItem(
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
AppText(
text = detail.bookName,
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
maxLines = 1
)
Text(
AppText(
text = detail.bookAuthor.ifBlank { "未知作者" },
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
AppText(
text = "阅读时长: ${formatDuring(detail.readTime)}",
color = MaterialTheme.colorScheme.outline,
style = MaterialTheme.typography.labelSmall
style = LegadoTheme.typography.labelSmall
)
}
}
@@ -872,28 +870,27 @@ fun DateHeader(
date: String,
dailyTotalTime: Long? = null
) {
val dateText = formatFriendlyDate(date)
SectionHeader(
CollapsibleHeader(
showIcon = false,
isCollapsed = false,
onToggle = { },
title = "",
titleContent = {
Column {
Text(
text = dateText,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.secondary
val dateText = formatFriendlyDate(date)
AppText(
text = dateText,
style = LegadoTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = LegadoTheme.colorScheme.primary
)
dailyTotalTime?.let { total ->
AppText(
text = "已读 ${formatDuring(total)}",
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.onSurface
)
dailyTotalTime?.let { total ->
Text(
text = "已读 ${formatDuring(total)}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface
)
}
}
},
detailContent = null,
horizontalArrangement = Arrangement.Start
}
)
}
@@ -915,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,28 +929,28 @@ fun ReadingSummaryCard(
Column(modifier = Modifier.weight(1f)) {
Text(
AppText(
text = title,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary
style = LegadoTheme.typography.labelLarge,
color = LegadoTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(4.dp))
Row(verticalAlignment = Alignment.Bottom) {
Text(
AppText(
text = "已读 ",
style = MaterialTheme.typography.titleMedium
style = LegadoTheme.typography.titleMedium
)
Text(
AppText(
text = "$bookCount",
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary,
style = LegadoTheme.typography.headlineMedium,
color = LegadoTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
)
Text(
AppText(
text = " 本书",
style = MaterialTheme.typography.titleMedium
style = LegadoTheme.typography.titleMedium
)
}
@@ -965,10 +960,10 @@ fun ReadingSummaryCard(
val minutes = totalDurationMinutes % 60
val timeString = if (hours > 0) "${hours}小时${minutes}分钟" else "${minutes}分钟"
Text(
AppText(
text = "共阅读 $timeString",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.onSurfaceVariant
)
}
@@ -44,7 +44,6 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
@@ -64,11 +63,9 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.SearchContentHistory
import io.legado.app.ui.widget.components.AnimatedText
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SearchBarSection
import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton
import io.legado.app.ui.widget.components.button.SmallIconButton
@@ -76,6 +73,9 @@ import io.legado.app.ui.widget.components.button.TopBarAnimatedActionButton
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
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 kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@@ -140,12 +140,9 @@ fun SearchContentScreen(
topBar = {
Column {
GlassMediumFlexibleTopAppBar(
title = {
val title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
"${searchResults.size} 条结果"
} else "搜索内容"
AnimatedText(text = title)
},
title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
"${searchResults.size} 条结果"
} else "搜索内容",
navigationIcon = { TopbarNavigationButton(onClick = onBack) },
actions = {
Row(
@@ -191,7 +188,7 @@ fun SearchContentScreen(
),
tooltip = {
PlainTooltip {
Text(if (isSearching) "停止搜索" else "跳转到当前章节")
AppText(if (isSearching) "停止搜索" else "跳转到当前章节")
}
},
state = rememberTooltipState(),
@@ -310,10 +307,10 @@ fun SearchHistoryList(
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(
AppText(
text = "搜索历史",
style = MaterialTheme.typography.titleSmallEmphasized,
color = MaterialTheme.colorScheme.primary,
style = LegadoTheme.typography.titleSmallEmphasized,
color = LegadoTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.Center)
)
SmallAnimatedActionButton(
@@ -342,7 +339,7 @@ fun SearchHistoryList(
.clickable { onHistoryClick(item) }
.animateItem(),
headlineContent = {
Text(
AppText(
text = item.query,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -378,7 +375,7 @@ fun SearchHistoryList(
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.width(8.dp))
Text("清除搜索历史")
AppText("清除搜索历史")
}
}
}
@@ -407,7 +404,7 @@ fun SearchResultItem(
Box(modifier = Modifier.padding(16.dp)) {
Column {
Text(
AppText(
text = buildAnnotatedString {
append(
result.getTitleSpannable(
@@ -415,14 +412,14 @@ fun SearchResultItem(
)
)
},
style = MaterialTheme.typography.titleSmall
style = LegadoTheme.typography.titleSmall
)
Spacer(modifier = Modifier.height(8.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(8.dp))
Text(
AppText(
text = buildAnnotatedString {
append(
result.getContentSpannable(
@@ -432,7 +429,7 @@ fun SearchResultItem(
)
)
},
style = MaterialTheme.typography.bodyMedium
style = LegadoTheme.typography.bodyMedium
)
}
@@ -63,7 +63,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleFloatingActionButton
import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon
import androidx.compose.material3.animateFloatingActionButton
@@ -95,11 +94,11 @@ import io.legado.app.data.entities.Bookmark
import io.legado.app.help.book.isLocal
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
import io.legado.app.ui.replace.ReplaceEditRoute
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem
@@ -109,7 +108,9 @@ import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
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.DynamicTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@@ -312,33 +313,33 @@ fun TocScreen(
when (pagerState.currentPage) {
0 -> {
RoundDropdownMenuItem(
text = { Text("使用替换规则") },
text = "使用替换规则",
trailingIcon = {
Checkbox(checked = useReplace, onCheckedChange = null)
},
onClick = { viewModel.toggleUseReplace() }
)
RoundDropdownMenuItem(
text = { Text("显示字数") },
text = "显示字数",
trailingIcon = {
Checkbox(checked = showWordCount, onCheckedChange = null)
},
onClick = { viewModel.toggleShowWordCount() }
)
RoundDropdownMenuItem(
text = { Text("反转目录") },
text = "反转目录",
onClick = { viewModel.reverseToc() }
)
PillDivider()
RoundDropdownMenuItem(
text = { Text("替换规则") },
text = "替换规则",
onClick = {
onOpenReplaceRule(null)
dismiss()
}
)
RoundDropdownMenuItem(
text = { Text("新建替换规则") },
text = "新建替换规则",
onClick = {
val scopes = mutableListOf<String>()
book?.name?.let { scopes.add(it) }
@@ -358,7 +359,7 @@ fun TocScreen(
if (book?.isLocal == true) {
PillHeaderDivider(title = "本地书籍选项")
RoundDropdownMenuItem(
text = { Text("本地书籍目录规则") },
text = "本地书籍目录规则",
onClick = {
val intent =
Intent(context, TxtTocRuleActivity::class.java).apply {
@@ -369,7 +370,7 @@ fun TocScreen(
}
)
RoundDropdownMenuItem(
text = { Text("拆分超长章节") },
text = "拆分超长章节",
trailingIcon = {
Checkbox(
checked = viewModel.isSplitLongChapter,
@@ -386,7 +387,7 @@ fun TocScreen(
else -> {
RoundDropdownMenuItem(
text = { Text("导出书签为JSON") },
text = "导出书签为JSON",
onClick = {
val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm",
@@ -398,7 +399,7 @@ fun TocScreen(
}
)
RoundDropdownMenuItem(
text = { Text("导出书签为MarkDown") },
text = "导出书签为MarkDown",
onClick = {
val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm",
@@ -428,7 +429,7 @@ fun TocScreen(
selected = pagerState.currentPage == 0,
onClick = { scope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(
AppText(
text = "目录",
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -439,7 +440,7 @@ fun TocScreen(
selected = pagerState.currentPage == 1,
onClick = { scope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(
AppText(
text = "书签",
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -464,13 +465,13 @@ fun TocScreen(
onDismissRequest = { showVolumeMenu = false }
) {
RoundDropdownMenuItem(
text = { Text("展开所有卷") },
text = "展开所有卷",
onClick = {
viewModel.expandAllVolumes(); showVolumeMenu = false
}
)
RoundDropdownMenuItem(
text = { Text("收起所有卷") },
text = "收起所有卷",
onClick = {
viewModel.collapseAllVolumes(); showVolumeMenu = false
}
@@ -482,7 +483,7 @@ fun TocScreen(
PillHeaderDivider(title = "快速跳转")
volumeItems.forEach { uiItem ->
RoundDropdownMenuItem(
text = { Text(uiItem.title) },
text = uiItem.title,
onClick = {
scope.launch {
val targetIndex =
@@ -541,7 +542,7 @@ fun TocScreen(
fabMenuExpanded = false
},
icon = { Icon(icon, contentDescription = null) },
text = { Text(text = label) }
text = { AppText(text = label) }
)
}
}
@@ -593,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
}
)
}
}
@@ -729,9 +729,9 @@ fun ChapterItem(
)
}
Text(
AppText(
text = item.title,
style = MaterialTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
style = LegadoTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -739,9 +739,9 @@ fun ChapterItem(
}
if (!item.tag.isNullOrEmpty()) {
Text(
AppText(
text = item.tag,
style = MaterialTheme.typography.labelSmallEmphasized,
style = LegadoTheme.typography.labelSmallEmphasized,
color = detailColor.copy(alpha = 0.8f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -910,11 +910,11 @@ private fun StatusIcon(
color = Color.Transparent
) {
if (wordCount != null) {
Text(
AppText(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 4.dp),
text = wordCount,
style = MaterialTheme.typography.labelSmall.copy(fontSize = 9.sp),
style = LegadoTheme.typography.labelSmall.copy(fontSize = 9.sp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
@@ -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
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@@ -51,7 +48,6 @@ 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
@@ -59,6 +55,7 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet
import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -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 {
Text(rule.name, style = MaterialTheme.typography.titleMedium)
Text(rule.rule, style = MaterialTheme.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) {
@@ -212,16 +204,16 @@ fun TxtRuleScreen(
showDeleteRuleDialog?.let { rule ->
AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) },
title = { AppText(stringResource(R.string.delete)) },
text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = {
OutlinedButton(onClick = {
viewModel.delete(rule); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) }
}) { AppText(stringResource(R.string.ok)) }
},
dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) {
Text(
AppText(
stringResource(R.string.cancel)
)
}
@@ -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 = { Text(stringResource(R.string.import_str)) },
text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -14,11 +13,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -36,7 +35,7 @@ fun ConfigNavScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.setting)) },
title = stringResource(R.string.setting),
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
@@ -19,7 +19,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
@@ -42,14 +41,15 @@ import io.legado.app.help.storage.Restore
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.InputSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.takePersistablePermissionSafely
import kotlinx.coroutines.launch
@@ -166,9 +166,7 @@ fun BackupConfigScreen(
},
topBar = {
GlassMediumFlexibleTopAppBar(
title = {
Text(stringResource(R.string.backup_restore))
},
title = stringResource(R.string.backup_restore),
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
@@ -387,19 +385,19 @@ fun BackupConfigScreen(
if (showWebDavAuthDialog) {
AlertDialog(
onDismissRequest = { showWebDavAuthDialog = false },
title = { Text(stringResource(R.string.web_dav_account)) },
title = { AppText(stringResource(R.string.web_dav_account)) },
text = {
Column {
TextField(
value = tempAccount,
onValueChange = { tempAccount = it },
label = { Text("账号") }
label = { AppText("账号") }
)
Spacer(modifier = Modifier.padding(8.dp))
TextField(
value = tempPassword,
onValueChange = { tempPassword = it },
label = { Text("密码") },
label = { AppText("密码") },
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
@@ -432,12 +430,12 @@ fun BackupConfigScreen(
}
}
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { showWebDavAuthDialog = false }) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -455,36 +453,35 @@ 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(
onDismissRequest = { showRestoreSheet = false },
title = { Text(stringResource(R.string.select_restore_file)) },
title = { AppText(stringResource(R.string.select_restore_file)) },
text = {
Column {
backupNames.forEach { name ->
@@ -508,7 +505,7 @@ fun BackupConfigScreen(
}
)
}) {
Text(name)
AppText(name)
}
}
}
@@ -516,7 +513,7 @@ fun BackupConfigScreen(
confirmButton = {},
dismissButton = {
TextButton(onClick = { showRestoreSheet = false }) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -535,7 +532,7 @@ fun BackupConfigScreen(
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
showBackupIgnoreDialog = false
},
title = { Text(stringResource(R.string.restore_ignore)) },
title = { AppText(stringResource(R.string.restore_ignore)) },
text = {
Column {
io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title ->
@@ -546,7 +543,7 @@ fun BackupConfigScreen(
] = checkedItems[index]
}) {
val isChecked = checkedItems[index]
Text(if (isChecked) "$title" else title)
AppText(if (isChecked) "$title" else title)
}
}
}
@@ -556,7 +553,7 @@ fun BackupConfigScreen(
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
showBackupIgnoreDialog = false
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
},
dismissButton = {
@@ -564,7 +561,7 @@ fun BackupConfigScreen(
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
showBackupIgnoreDialog = false
}) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -573,7 +570,7 @@ fun BackupConfigScreen(
if (showLoadingDialog) {
AlertDialog(
onDismissRequest = {},
title = { Text(loadingText) },
title = { AppText(loadingText) },
confirmButton = {},
dismissButton = {}
)
@@ -589,16 +586,16 @@ fun ConfirmDialog(
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(title) },
text = { Text(text) },
title = { AppText(title) },
text = { AppText(text) },
confirmButton = {
OutlinedButton(onClick = onConfirm) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -12,7 +12,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -27,14 +26,14 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@@ -52,7 +51,7 @@ fun CoverConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.cover_config)) },
title = stringResource(R.string.cover_config),
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
@@ -247,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) {
@@ -269,6 +268,7 @@ fun CoverConfigScreen(
}
ColorPickerSheet(
show = showColorPickerByField != null,
initialColor = initialColor,
onDismissRequest = { showColorPickerByField = null },
onColorSelected = { color ->
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -36,12 +35,15 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
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
@@ -61,7 +63,8 @@ fun CoverManageSheet(
}
val coverList = currentCovers.split(",").filter { it.isNotBlank() }
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -69,9 +72,9 @@ fun CoverManageSheet(
.fillMaxWidth()
.padding(bottom = 24.dp)
) {
Text(
AppText(
text = stringResource(R.string.default_cover),
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp)
)
@@ -141,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")
)
}
@@ -13,7 +13,6 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -27,8 +26,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.model.BookCover
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.theme.LegadoTheme
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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -37,6 +38,7 @@ import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CoverRuleConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit
) {
var enable by remember { mutableStateOf(false) }
@@ -52,7 +54,8 @@ fun CoverRuleConfigSheet(
coverRule = rule.coverRule
}
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -61,9 +64,9 @@ fun CoverRuleConfigSheet(
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text(
AppText(
text = stringResource(R.string.cover_rule),
style = MaterialTheme.typography.titleLarge,
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
@@ -75,7 +78,7 @@ fun CoverRuleConfigSheet(
checked = enable,
onCheckedChange = { enable = it }
)
Text(text = stringResource(R.string.enable))
AppText(text = stringResource(R.string.enable))
}
InputSettingItem(
@@ -100,13 +103,16 @@ fun CoverRuleConfigSheet(
BookCover.delCoverRule()
onDismissRequest()
}) {
Text(stringResource(R.string.delete), color = MaterialTheme.colorScheme.error)
AppText(
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
}
Spacer(modifier = Modifier.weight(1f))
OutlinedButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
Spacer(modifier = Modifier.padding(horizontal = 4.dp))
@@ -121,7 +127,7 @@ fun CoverRuleConfigSheet(
onDismissRequest()
}
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
}
}
@@ -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 androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -18,21 +17,25 @@ import androidx.compose.ui.platform.LocalContext
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.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(
@@ -41,9 +44,9 @@ fun CheckSourceBottomSheet(
.padding(horizontal = 16.dp)
.padding(bottom = 32.dp)
) {
Text(
AppText(
text = stringResource(R.string.check_source_config),
style = MaterialTheme.typography.titleLarge,
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp)
)
@@ -131,7 +134,7 @@ fun CheckSourceBottomSheet(
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
Button(onClick = {
if (viewModel.saveCheckSourceConfig()) {
@@ -140,7 +143,7 @@ fun CheckSourceBottomSheet(
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show()
}
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
}
}
@@ -24,7 +24,6 @@ import androidx.compose.material3.IconButton
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.TopAppBarDefaults
import androidx.compose.runtime.Composable
@@ -41,8 +40,10 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
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
import io.legado.app.utils.getClipText
@@ -52,6 +53,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DirectLinkUploadBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel,
onDismiss: () -> Unit
) {
@@ -63,7 +65,8 @@ fun DirectLinkUploadBottomSheet(
viewModel.initDirectLinkRule()
}
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss
) {
Column(
@@ -75,9 +78,9 @@ fun DirectLinkUploadBottomSheet(
) {
CenterAlignedTopAppBar(
title = {
Text(
AppText(
text = stringResource(R.string.direct_link_upload_config),
style = MaterialTheme.typography.titleLarge
style = LegadoTheme.typography.titleLarge
)
},
actions = {
@@ -89,7 +92,7 @@ fun DirectLinkUploadBottomSheet(
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
text = { Text("导入默认") },
text = { AppText("导入默认") },
leadingIcon = { Icon(Icons.Default.Download, null) },
onClick = {
showMenu = false
@@ -99,7 +102,7 @@ fun DirectLinkUploadBottomSheet(
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.copy_rule)) },
text = { AppText(stringResource(R.string.copy_rule)) },
leadingIcon = { Icon(Icons.Default.ContentCopy, null) },
onClick = {
showMenu = false
@@ -113,7 +116,7 @@ fun DirectLinkUploadBottomSheet(
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.paste_rule)) },
text = { AppText(stringResource(R.string.paste_rule)) },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = {
showMenu = false
@@ -141,7 +144,7 @@ fun DirectLinkUploadBottomSheet(
OutlinedTextField(
value = viewModel.uploadUrl,
onValueChange = { viewModel.uploadUrl = it },
label = { Text(stringResource(R.string.upload_url)) },
label = { AppText(stringResource(R.string.upload_url)) },
modifier = Modifier.fillMaxWidth(),
)
@@ -150,7 +153,7 @@ fun DirectLinkUploadBottomSheet(
OutlinedTextField(
value = viewModel.downloadUrlRule,
onValueChange = { viewModel.downloadUrlRule = it },
label = { Text(stringResource(R.string.download_url_rule)) },
label = { AppText(stringResource(R.string.download_url_rule)) },
modifier = Modifier.fillMaxWidth(),
)
@@ -159,7 +162,7 @@ fun DirectLinkUploadBottomSheet(
OutlinedTextField(
value = viewModel.summary,
onValueChange = { viewModel.summary = it },
label = { Text(stringResource(R.string.summary)) },
label = { AppText(stringResource(R.string.summary)) },
modifier = Modifier.fillMaxWidth(),
)
@@ -178,11 +181,11 @@ fun DirectLinkUploadBottomSheet(
TextButton(onClick = {
viewModel.testRule { result -> showTestResult = result }
}) {
Text("测试")
AppText("测试")
}
Spacer(Modifier.weight(1f))
OutlinedButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
Button(onClick = {
if (viewModel.saveDirectLinkRule()) {
@@ -191,7 +194,7 @@ fun DirectLinkUploadBottomSheet(
context.toastOnUi("请填写完整信息")
}
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
}
}
@@ -200,15 +203,15 @@ fun DirectLinkUploadBottomSheet(
showTestResult?.let { result ->
AlertDialog(
onDismissRequest = { showTestResult = null },
title = { Text("Result") },
text = { Text(result) },
title = { AppText("Result") },
text = { AppText(result) },
confirmButton = {
TextButton(onClick = {
showTestResult = null
}) { Text(stringResource(R.string.ok)) }
}) { AppText(stringResource(R.string.ok)) }
},
dismissButton = {
TextButton(onClick = { context.sendToClip(result) }) { Text(stringResource(R.string.copy_text)) }
TextButton(onClick = { context.sendToClip(result) }) { AppText(stringResource(R.string.copy_text)) }
}
)
}
@@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
@@ -30,8 +29,6 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.service.WebService
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
@@ -40,6 +37,9 @@ import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.InputSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.utils.restart
import io.legado.app.utils.takePersistablePermissionSafely
import org.koin.androidx.compose.koinViewModel
@@ -83,9 +83,7 @@ fun OtherConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = {
Text(stringResource(R.string.other_setting))
},
title = stringResource(R.string.other_setting),
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
@@ -200,7 +198,10 @@ fun OtherConfigScreen(
SliderSettingItem(
title = stringResource(R.string.bitmap_cache_size),
description = stringResource(R.string.bitmap_cache_size_summary),
description = stringResource(
R.string.bitmap_cache_size_summary,
OtherConfig.bitmapCacheSize
),
value = OtherConfig.bitmapCacheSize.toFloat(),
defaultValue = 32f,
valueRange = 1f..2047f,
@@ -211,7 +212,10 @@ fun OtherConfigScreen(
SliderSettingItem(
title = stringResource(R.string.image_retain_number),
description = stringResource(R.string.image_retain_number_summary),
description = stringResource(
R.string.image_retain_number_summary,
OtherConfig.imageRetainNum
),
value = OtherConfig.imageRetainNum.toFloat(),
defaultValue = 10f,
valueRange = 0f..100f,
@@ -220,7 +224,10 @@ fun OtherConfigScreen(
SliderSettingItem(
title = stringResource(R.string.pre_download),
description = stringResource(R.string.pre_download_s),
description = stringResource(
R.string.pre_download_s,
OtherConfig.preDownloadNum
),
value = OtherConfig.preDownloadNum.toFloat(),
defaultValue = 10f,
valueRange = 0f..100f,
@@ -391,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(
@@ -458,23 +462,23 @@ fun OtherConfigScreen(
if (showPasswordDialog) {
AlertDialog(
onDismissRequest = { showPasswordDialog = false },
title = { Text(stringResource(R.string.set_local_password)) },
title = { AppText(stringResource(R.string.set_local_password)) },
text = {
TextField(
value = tempPassword,
onValueChange = { tempPassword = it },
label = { Text("Password") }
label = { AppText("Password") }
)
},
confirmButton = {
TextButton(onClick = {
viewModel.setLocalPassword(tempPassword)
showPasswordDialog = false
}) { Text(stringResource(R.string.ok)) }
}) { AppText(stringResource(R.string.ok)) }
},
dismissButton = {
TextButton(onClick = { showPasswordDialog = false }) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -492,16 +496,16 @@ fun ConfirmDialog(
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(title) },
text = { Text(text) },
title = { AppText(title) },
text = { AppText(text) },
confirmButton = {
OutlinedButton(onClick = onConfirm) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -11,7 +11,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -23,33 +22,36 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
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.theme.LegadoTheme
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()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
AppText(
text = stringResource(R.string.custom_page_key),
style = MaterialTheme.typography.titleLarge,
style = LegadoTheme.typography.titleLarge,
fontWeight = FontWeight.Companion.Bold
)
OutlinedTextField(
value = prevKeys,
onValueChange = { prevKeys = it },
label = { Text(stringResource(R.string.prev_page_key)) },
label = { AppText(stringResource(R.string.prev_page_key)) },
modifier = Modifier.Companion
.fillMaxWidth()
.onPreviewKeyEvent { event ->
@@ -72,7 +74,7 @@ fun PageKeySheet(
OutlinedTextField(
value = nextKeys,
onValueChange = { nextKeys = it },
label = { Text(stringResource(R.string.next_page_key)) },
label = { AppText(stringResource(R.string.next_page_key)) },
modifier = Modifier.Companion
.fillMaxWidth()
.onPreviewKeyEvent { event ->
@@ -92,9 +94,9 @@ fun PageKeySheet(
singleLine = true
)
Text(
AppText(
text = stringResource(R.string.page_key_set_help),
style = MaterialTheme.typography.bodyMedium,
style = LegadoTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -109,7 +111,7 @@ fun PageKeySheet(
},
modifier = Modifier.Companion.weight(1f)
) {
Text(stringResource(R.string.reset))
AppText(stringResource(R.string.reset))
}
Button(
@@ -120,7 +122,7 @@ fun PageKeySheet(
},
modifier = Modifier.Companion.weight(1f)
) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
}
}
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -20,14 +19,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import org.koin.androidx.compose.koinViewModel
@@ -45,9 +44,7 @@ fun ReadConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = {
Text(stringResource(R.string.read_config))
},
title = stringResource(R.string.read_config),
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
@@ -323,7 +320,8 @@ fun ReadConfigScreen(
}
}
if (showPageKeySheet) {
PageKeySheet(onDismissRequest = { showPageKeySheet = false })
}
PageKeySheet(
show = showPageKeySheet,
onDismissRequest = { showPageKeySheet = false }
)
}
@@ -17,7 +17,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -31,14 +30,17 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BackgroundImageManageSheet(
show: Boolean,
isDarkTheme: Boolean,
onDismissRequest: () -> Unit,
viewModel: ThemeConfigViewModel = koinViewModel()
@@ -64,7 +66,8 @@ fun BackgroundImageManageSheet(
ThemeConfig.bgImageLight
}
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -72,9 +75,9 @@ fun BackgroundImageManageSheet(
.fillMaxWidth()
.padding(bottom = 24.dp)
) {
Text(
AppText(
text = stringResource(R.string.background_image),
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp)
)
@@ -139,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")
)
}
@@ -20,7 +20,6 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -39,13 +38,16 @@ import io.legado.app.ui.main.Launcher4
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.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.theme.LegadoTheme
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
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LauncherIconPickerSheet(
show: Boolean,
selectedValue: String,
onDismissRequest: () -> Unit,
onValueChange: (String) -> Unit
@@ -53,7 +55,8 @@ fun LauncherIconPickerSheet(
val context = LocalContext.current
val icons = LauncherIcons.list
GlassModalBottomSheet(
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest
) {
Column(
@@ -61,9 +64,9 @@ fun LauncherIconPickerSheet(
.fillMaxWidth()
.padding(bottom = 24.dp)
) {
Text(
AppText(
text = stringResource(R.string.change_icon),
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp)
)
@@ -15,12 +15,15 @@ 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, "material")
var materialVersion by prefDelegate(PreferKey.materialVersion, "material3")
var appTheme by prefDelegate(PreferKey.appTheme, "0")
@@ -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
@@ -78,11 +79,10 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.OldThemeConfig
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeManager
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
@@ -90,10 +90,17 @@ import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.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
@@ -121,7 +128,7 @@ fun ThemeConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.theme_setting)) },
title = stringResource(R.string.theme_setting),
scrollBehavior = scrollBehavior,
navigationIcon = {
TopbarNavigationButton(onClick = onBackClick)
@@ -136,23 +143,29 @@ fun ThemeConfigScreen(
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
val composeEngine = ThemeConfig.composeEngine
val isMiuixEngine = remember(composeEngine) {
ThemeResolver.isMiuixEngine(composeEngine)
}
val isDarkTheme = when (selectedThemeMode) {
"1" -> false
"2" -> true
else -> isSystemInDarkTheme()
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
ThemeCard(
context = context,
value = selectedTheme,
isDark = isDarkTheme,
isAmoled = ThemeConfig.isPureBlack,
paletteStyle = ThemeConfig.paletteStyle
)
if (!isMiuixEngine) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
ThemeCard(
context = context,
value = selectedTheme,
isDark = isDarkTheme,
isAmoled = ThemeConfig.isPureBlack,
paletteStyle = ThemeConfig.paletteStyle
)
}
}
Spacer(modifier = Modifier.height(16.dp))
@@ -163,47 +176,77 @@ 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)) {
ThemeModeSelector(
selectedMode = selectedThemeMode,
onModeSelected = { mode ->
selectedThemeMode = mode
ThemeConfig.themeMode = mode
OldThemeConfig.applyDayNight(context)
}
)
if (isMiuixEngine) {
DropdownListSettingItem(
title = stringResource(R.string.theme_mode),
selectedValue = selectedThemeMode,
displayEntries = stringArrayResource(R.array.theme_mode),
entryValues = stringArrayResource(R.array.theme_mode_v),
onValueChange = { mode ->
selectedThemeMode = mode
ThemeConfig.themeMode = mode
OldThemeConfig.applyDayNight(context)
}
)
} else {
ThemeModeSelector(
selectedMode = selectedThemeMode,
onModeSelected = { mode ->
selectedThemeMode = mode
ThemeConfig.themeMode = mode
OldThemeConfig.applyDayNight(context)
}
)
}
Spacer(modifier = Modifier.height(16.dp))
if (!isMiuixEngine) {
Spacer(modifier = Modifier.height(16.dp))
ThemeColorSelector(
context = context,
themes = themes,
selectedTheme = selectedTheme,
isDark = isDarkTheme,
isAmoled = ThemeConfig.isPureBlack,
paletteStyle = ThemeConfig.paletteStyle,
onThemeSelected = { theme ->
if (theme == "13") {
val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
if (!hasLightBg || !hasDarkBg) {
context.toastOnUi(R.string.transparent_theme_alarm)
return@ThemeColorSelector
ThemeColorSelector(
context = context,
themes = themes,
selectedTheme = selectedTheme,
isDark = isDarkTheme,
isAmoled = ThemeConfig.isPureBlack,
paletteStyle = ThemeConfig.paletteStyle,
onThemeSelected = { theme ->
if (theme == "13") {
val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
if (!hasLightBg || !hasDarkBg) {
context.toastOnUi(R.string.transparent_theme_alarm)
return@ThemeColorSelector
} else {
AppConfig.containerOpacity = 0
}
}
val oldTheme = selectedTheme
selectedTheme = theme
ThemeConfig.appTheme = theme
val isDynamicSwitch = (oldTheme == "12" || theme == "12")
if (isDynamicSwitch) {
showRestartDialog = true
} else {
AppConfig.containerOpacity = 0
postEvent(EventBus.RECREATE, "")
}
}
val oldTheme = selectedTheme
selectedTheme = theme
ThemeConfig.appTheme = theme
val isDynamicSwitch = (oldTheme == "12" || theme == "12")
if (isDynamicSwitch) {
showRestartDialog = true
} else {
postEvent(EventBus.RECREATE, "")
}
}
)
)
}
}
SplicedColumnGroup {
@@ -226,13 +269,22 @@ fun ThemeConfigScreen(
context.toastOnUi(R.string.restart_to_apply)
}
)
DropdownListSettingItem(
title = stringResource(R.string.compose_engine),
selectedValue = ThemeConfig.composeEngine,
displayEntries = stringArrayResource(R.array.composeEngine),
entryValues = stringArrayResource(R.array.composeEngine_value),
onValueChange = {
ThemeConfig.composeEngine = it
}
)
SliderSettingItem(
title = stringResource(R.string.font_scale),
description = stringResource(
R.string.font_scale_summary,
AppContextWrapper.getFontScale(context)
),
value = fontScaleValue.value,
value = fontScaleValue.floatValue,
defaultValue = 10f,
valueRange = 8f..16f,
steps = 7,
@@ -478,7 +530,7 @@ fun ThemeConfigScreen(
if (showRestartDialog) {
AlertDialog(
onDismissRequest = { showRestartDialog = false },
title = { Text(stringResource(R.string.restart_required_message)) },
title = { AppText(stringResource(R.string.restart_required_message)) },
confirmButton = {
OutlinedButton(onClick = {
showRestartDialog = false
@@ -486,7 +538,7 @@ fun ThemeConfigScreen(
context.restart()
}, 100)
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
},
dismissButton = {
@@ -494,7 +546,7 @@ fun ThemeConfigScreen(
showRestartDialog = false
context.toastOnUi(R.string.restart_later_message)
}) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -502,57 +554,56 @@ 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(
onDismissRequest = { saveThemeKey = null },
title = { Text(stringResource(R.string.theme_name)) },
title = { AppText(stringResource(R.string.theme_name)) },
text = {
OutlinedTextField(
value = themeName,
onValueChange = { themeName = it },
label = { Text("name") },
label = { AppText("name") },
singleLine = true
)
},
@@ -566,12 +617,12 @@ fun ThemeConfigScreen(
saveThemeKey = null
}
) {
Text(stringResource(android.R.string.ok))
AppText(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { saveThemeKey = null }) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
}
)
@@ -745,9 +796,9 @@ fun ThemeColorButton(
Spacer(modifier = Modifier.height(12.dp))
Text(
AppText(
text = label,
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
)
}
@@ -15,12 +15,10 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.FileDownload
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -38,8 +36,10 @@ import androidx.core.graphics.toColorInt
import io.legado.app.R
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
import io.legado.app.utils.share
@@ -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(
@@ -69,9 +71,9 @@ fun ThemeListDialog(
.padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
AppText(
text = stringResource(R.string.theme_list),
style = MaterialTheme.typography.titleMedium,
style = LegadoTheme.typography.titleMedium,
modifier = Modifier.weight(1f)
)
IconButton(
@@ -104,13 +106,11 @@ fun ThemeListDialog(
.fillMaxWidth()
.padding(horizontal = 16.dp),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(
containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.surfaceContainer
}
)
containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.surfaceContainer
}
) {
Row(
modifier = Modifier
@@ -129,9 +129,9 @@ fun ThemeListDialog(
)
.padding(8.dp)
)
Text(
AppText(
text = item.themeName,
style = MaterialTheme.typography.bodyLarge,
style = LegadoTheme.typography.bodyLarge,
modifier = Modifier.weight(1f)
)
IconButton(
@@ -161,8 +161,8 @@ fun ThemeListDialog(
deleteIndex?.let { index ->
AlertDialog(
onDismissRequest = { deleteIndex = null },
title = { Text(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.sure_del)) },
title = { AppText(stringResource(R.string.delete)) },
text = { AppText(stringResource(R.string.sure_del)) },
confirmButton = {
TextButton(
onClick = {
@@ -171,12 +171,12 @@ fun ThemeListDialog(
deleteIndex = null
}
) {
Text(stringResource(android.R.string.ok))
AppText(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = { deleteIndex = null }) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
}
)
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -37,7 +36,8 @@ 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
@OptIn(ExperimentalMaterial3Api::class)
@@ -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(
@@ -69,7 +70,7 @@ fun DictRuleEditSheet(
.verticalScroll(rememberScrollState())
) {
CenterAlignedTopAppBar(
title = { Text(stringResource(R.string.dict_rule)) },
title = { AppText(stringResource(R.string.dict_rule)) },
navigationIcon = {
IconButton(onClick = onDismissRequest) {
Icon(
@@ -87,7 +88,7 @@ fun DictRuleEditSheet(
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.copy_rule)) },
text = { AppText(stringResource(R.string.copy_rule)) },
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
onClick = {
onCopy(
@@ -102,7 +103,7 @@ fun DictRuleEditSheet(
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.paste_rule)) },
text = { AppText(stringResource(R.string.paste_rule)) },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = {
scope.launch {
@@ -132,20 +133,20 @@ fun DictRuleEditSheet(
modifier = Modifier.fillMaxWidth(),
value = name,
onValueChange = { name = it },
label = { Text(stringResource(R.string.name)) },
label = { AppText(stringResource(R.string.name)) },
singleLine = true
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = urlRule,
onValueChange = { urlRule = it },
label = { Text(stringResource(R.string.url_rule)) }
label = { AppText(stringResource(R.string.url_rule)) }
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = showRule,
onValueChange = { showRule = it },
label = { Text(stringResource(R.string.show_rule)) },
label = { AppText(stringResource(R.string.show_rule)) },
minLines = 3
)
}
@@ -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
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@@ -51,7 +48,6 @@ 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
@@ -59,6 +55,7 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet
import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -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 {
Text(rule.name, style = MaterialTheme.typography.titleMedium)
Text(rule.urlRule, style = MaterialTheme.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) {
@@ -211,16 +203,16 @@ fun DictRuleScreen(
showDeleteRuleDialog?.let { rule ->
AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) },
title = { AppText(stringResource(R.string.delete)) },
text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = {
OutlinedButton(onClick = {
viewModel.delete(rule); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) }
}) { AppText(stringResource(R.string.ok)) }
},
dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) {
Text(
AppText(
stringResource(R.string.cancel)
)
}
@@ -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 = { Text(stringResource(R.string.import_str)) },
text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
@@ -181,7 +181,7 @@ open class MainActivity : BaseComposeActivity() {
(slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween(
durationMillis = 520,
durationMillis = 480,
easing = FastOutSlowInEasing
),
initialOffset = { fullWidth -> fullWidth }
@@ -193,7 +193,7 @@ open class MainActivity : BaseComposeActivity() {
)) togetherWith (slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween(
durationMillis = 520,
durationMillis = 480,
easing = FastOutSlowInEasing
),
targetOffset = { fullWidth -> fullWidth / 4 }
@@ -208,7 +208,7 @@ open class MainActivity : BaseComposeActivity() {
(slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween(
durationMillis = 520,
durationMillis = 480,
easing = FastOutSlowInEasing
),
initialOffset = { fullWidth -> -fullWidth / 4 }
@@ -220,7 +220,7 @@ open class MainActivity : BaseComposeActivity() {
)) togetherWith (scaleOut(
targetScale = 0.8f,
animationSpec = tween(
durationMillis = 520,
durationMillis = 480,
easing = FastOutSlowInEasing
)
) + fadeOut(
@@ -12,42 +12,57 @@ import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.RssFeed
import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.R
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.extended.Album
import top.yukonga.miuix.kmp.icon.extended.ContactsBook
import top.yukonga.miuix.kmp.icon.extended.Favorites
import top.yukonga.miuix.kmp.icon.extended.Settings
sealed class MainDestination(
val route: String,
@StringRes val labelId: Int,
val icon: ImageVector,
val selectedIcon: ImageVector
val m3Icon: ImageVector,
val m3SelectedIcon: ImageVector,
val miuixIcon: ImageVector,
val miuixSelectedIcon: ImageVector
) {
object Bookshelf : MainDestination(
"bookshelf",
R.string.bookshelf,
Icons.AutoMirrored.Outlined.LibraryBooks,
Icons.AutoMirrored.Filled.LibraryBooks
route = "bookshelf",
labelId = R.string.bookshelf,
m3Icon = Icons.AutoMirrored.Outlined.LibraryBooks,
m3SelectedIcon = Icons.AutoMirrored.Filled.LibraryBooks,
miuixIcon = MiuixIcons.Regular.ContactsBook, // 替换为实际的 MIUIX 线框图标
miuixSelectedIcon = MiuixIcons.Heavy.ContactsBook // 替换为实际的 MIUIX 填充图标
)
object Explore : MainDestination(
"explore",
R.string.discovery,
Icons.Outlined.Explore,
Icons.Default.Explore
route = "explore",
labelId = R.string.discovery,
m3Icon = Icons.Outlined.Explore,
m3SelectedIcon = Icons.Default.Explore,
miuixIcon = MiuixIcons.Regular.Album,
miuixSelectedIcon = MiuixIcons.Heavy.Album
)
object Rss : MainDestination(
"rss",
R.string.rss,
Icons.Outlined.RssFeed,
Icons.Default.RssFeed
route = "rss",
labelId = R.string.rss,
m3Icon = Icons.Outlined.RssFeed,
m3SelectedIcon = Icons.Default.RssFeed,
miuixIcon = MiuixIcons.Regular.Favorites,
miuixSelectedIcon = MiuixIcons.Heavy.Favorites
)
object My : MainDestination(
"my",
R.string.my,
Icons.Outlined.Person,
Icons.Default.Person
route = "my",
labelId = R.string.my,
m3Icon = Icons.Outlined.Person,
m3SelectedIcon = Icons.Default.Person,
miuixIcon = MiuixIcons.Regular.Settings,
miuixSelectedIcon = MiuixIcons.Heavy.Settings
)
companion object {
val mainDestinations = listOf(Bookshelf, Explore, Rss, My)
}
}
}
@@ -20,16 +20,12 @@ import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem
@@ -56,17 +52,19 @@ import io.legado.app.R
import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.mainConfig.MainConfig
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.main.bookshelf.BookshelfScreen
import io.legado.app.ui.main.bookshelf.BookshelfViewModel
import io.legado.app.ui.main.explore.ExploreScreen
import io.legado.app.ui.main.my.MyScreen
import io.legado.app.ui.main.rss.RssScreen
import io.legado.app.ui.theme.regularHazeEffect
import io.legado.app.ui.widget.components.AppNavigationBar
import io.legado.app.ui.widget.components.AppNavigationBarItem
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassDefaults
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.utils.startActivity
import io.legado.app.utils.startActivityForBook
import kotlinx.coroutines.launch
@@ -151,7 +149,7 @@ fun MainScreen(
onClick = { context.startActivity<SearchActivity>() },
expanded = expanded,
icon = { Icon(Icons.Default.Search, contentDescription = null) },
text = { Text(stringResource(R.string.search)) }
text = { AppText(stringResource(R.string.search)) }
)
}
}
@@ -198,7 +196,7 @@ fun MainScreen(
) { dismiss ->
bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
RoundDropdownMenuItem(
text = { Text(group.groupName) },
text = group.groupName,
onClick = {
coroutineScope.launch {
if (pagerState.currentPage != index) {
@@ -224,7 +222,7 @@ fun MainScreen(
}
},
label = if (labelVisibilityMode != "unlabeled") {
{ Text(stringResource(destination.labelId)) }
{ AppText(stringResource(destination.labelId)) }
} else null
)
}
@@ -237,103 +235,38 @@ fun MainScreen(
if (!useRail && MainConfig.showBottomView) {
val labelVisibilityMode = MainConfig.labelVisibilityMode
val isUnlabeled = labelVisibilityMode == "unlabeled"
NavigationBar(
AppNavigationBar(
modifier = Modifier
.regularHazeEffect(state = hazeState)
.height(if (isUnlabeled) 64.dp else 80.dp),
containerColor = GlassDefaults.glassColor(
noBlurColor = BottomAppBarDefaults.containerColor,
blurAlpha = GlassDefaults.DefaultBlurAlpha
).let { baseColor ->
val opacity = (ThemeConfig.bottomBarOpacity.coerceIn(0, 100)) / 100f
baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
}
.height(if (isUnlabeled) 64.dp else 80.dp)
) {
val alwaysShowLabel = when (labelVisibilityMode) {
"labeled" -> true
"selected" -> false
"unlabeled" -> false
else -> false
}
destinations.forEachIndexed { index, destination ->
val selected = pagerState.targetPage == index
var showGroupMenu by remember { mutableStateOf(false) }
val haptic = LocalHapticFeedback.current
NavigationBarItem(
AppNavigationBarItem(
selected = selected,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
coroutineScope.launch { pagerState.animateScrollToPage(index) }
},
icon = {
Box {
NavigationIcon(
destination = destination,
selected = selected,
upBooksCount = uiState.upBooksCount,
modifier = if (destination == MainDestination.Bookshelf) {
Modifier.combinedClickable(
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
onLongClick = {
haptic.performHapticFeedback(
HapticFeedbackType.LongPress
)
showGroupMenu = true
}
)
} else Modifier
)
if (destination == MainDestination.Bookshelf && showGroupMenu) {
RoundDropdownMenu(
expanded = showGroupMenu,
onDismissRequest = { showGroupMenu = false }
) { dismiss ->
bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
RoundDropdownMenuItem(
text = { Text(group.groupName) },
onClick = {
coroutineScope.launch {
if (pagerState.currentPage != index) {
pagerState.scrollToPage(index)
}
bookshelfViewModel.changeGroup(
group.groupId
)
dismiss()
}
},
trailingIcon = {
if (bookshelfUiState.selectedGroupIndex == groupIndex) {
Icon(
Icons.Default.Check,
null,
modifier = Modifier.size(18.dp)
)
}
}
)
}
}
}
}
labelString = stringResource(destination.labelId),
miuixIcon = if (selected) destination.miuixSelectedIcon else destination.miuixIcon,
m3Icon = {
NavigationIcon(
destination = destination,
selected = selected,
upBooksCount = uiState.upBooksCount
)
},
colors = NavigationBarItemDefaults.colors(
indicatorColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
blurAlpha = GlassDefaults.ThickBlurAlpha
),
m3IndicatorColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
blurAlpha = GlassDefaults.ThickBlurAlpha
),
label = if (labelVisibilityMode != "unlabeled") {
{ Text(stringResource(destination.labelId)) }
} else null,
alwaysShowLabel = alwaysShowLabel
m3ShowLabel = !isUnlabeled,
m3AlwaysShowLabel = alwaysShowLabel
)
}
}
@@ -386,7 +319,7 @@ private fun NavigationIcon(
upBooksCount: Int,
modifier: Modifier = Modifier
) {
val icon = if (selected) destination.selectedIcon else destination.icon
val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon
Box(modifier = modifier) {
if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) {
@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -34,9 +33,11 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.cover.BookCover
import io.legado.app.ui.widget.components.cover.BookshelfCover
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toTimeAgo
/**
@@ -88,9 +89,9 @@ fun BookshelfItem(
) {
cover(Modifier.fillMaxSize())
if (gridStyle == 1) {
Text(
AppText(
text = title,
style = (if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium).copy(
style = (if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium).copy(
color = Color.White,
shadow = Shadow(
color = Color.Black.copy(alpha = 0.5f),
@@ -117,9 +118,9 @@ fun BookshelfItem(
}
if (gridStyle == 0) {
Text(
AppText(
text = title,
style = if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium,
style = if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium,
maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis,
textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
@@ -152,16 +153,16 @@ fun BookshelfItem(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.Center
) {
Text(
AppText(
text = title,
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
maxLines = if (!isCompact) 2 else 1,
overflow = TextOverflow.Ellipsis
)
subTitle?.let {
Text(
AppText(
text = it,
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(top = 2.dp)
@@ -169,9 +170,9 @@ fun BookshelfItem(
}
if (!isCompact) {
desc?.let {
Text(
AppText(
text = it,
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(top = 2.dp)
@@ -372,7 +373,7 @@ fun BookItem(
isUpdating = isUpdating,
modifier = modifier,
badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
showBadgeDot = !BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && unreadCount > 0
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew
)
},
title = book.name,
@@ -384,16 +385,16 @@ fun BookItem(
desc = stringResource(R.string.read_dur_progress, book.durChapterTitle ?: ""),
extra = {
if (BookshelfConfig.showLastUpdateTime && !book.isLocal) {
Text(
AppText(
text = book.latestChapterTime.toTimeAgo(),
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(end = 4.dp)
)
}
Text(
AppText(
text = book.latestChapterTitle ?: "",
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
@@ -16,6 +16,7 @@ data class BookShelfItem(
val durChapterPos: Int,
val latestChapterTitle: String?,
val latestChapterTime: Long,
val lastCheckCount: Int,
val totalChapterNum: Int,
val durChapterIndex: Int,
val type: Int,
@@ -33,5 +34,8 @@ data class BookShelfItem(
val isNotShelf: Boolean get() = (type and BookType.notShelf) > 0
val isNew: Boolean get() = lastCheckCount > 0
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
}
@@ -23,7 +23,6 @@ import androidx.compose.material.icons.filled.TextFormat
import androidx.compose.material.icons.filled.ViewCompact
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
@@ -32,7 +31,8 @@ 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.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
@@ -40,28 +40,28 @@ import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
@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)
) {
Text(
text = stringResource(R.string.bookshelf_layout),
style = MaterialTheme.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 androidx.compose.material3.Text
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
@@ -78,6 +73,7 @@ import io.legado.app.ui.book.import.remote.RemoteBookActivity
import io.legado.app.ui.book.manage.BookshelfManageActivity
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
@@ -85,6 +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.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 +235,37 @@ fun BookshelfScreen(
topBarActions = { },
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { Text(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 = { Text(stringResource(R.string.book_local)) },
text = stringResource(R.string.book_local),
onClick = { context.startActivity<ImportBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Save, null) }
)
RoundDropdownMenuItem(
text = { Text(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 = { Text("布局设置") },
text = "布局设置",
onClick = { showConfigSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.GridView, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.group_manage)) },
text = stringResource(R.string.group_manage),
onClick = { showGroupManageSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Edit, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.add_url)) },
text = stringResource(R.string.add_url),
onClick = { showAddUrlDialog = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Link, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.bookshelf_management)) },
text = stringResource(R.string.bookshelf_management),
onClick = {
val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -279,7 +277,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Settings, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.cache_export)) },
text = stringResource(R.string.cache_export),
onClick = {
val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -291,7 +289,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Download, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.export_bookshelf)) },
text = stringResource(R.string.export_bookshelf),
onClick = {
showExportSheet = true
dismiss()
@@ -299,12 +297,12 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.ImportExport, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_bookshelf)) },
text = stringResource(R.string.import_bookshelf),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.log)) },
text = stringResource(R.string.log),
onClick = {
showLogSheet = true
dismiss()
@@ -322,33 +320,19 @@ fun BookshelfScreen(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
PrimaryScrollableTabRow(
selectedTabIndex = selectedTabIndex,
edgePadding = 0.dp,
divider = { },
containerColor = Color.Transparent,
minTabWidth = 0.dp,
modifier = Modifier.weight(1f)
) {
uiState.groups.forEachIndexed { index, group ->
Tab(
selected = selectedTabIndex == index,
onClick = {
scope.launch { pagerState.animateScrollToPage(index) }
},
text = {
Text(
text = group.groupName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
)
}
val tabTitles = remember(uiState.groups) {
uiState.groups.map { it.groupName }
}
AppTabRow(
tabTitles = tabTitles,
selectedTabIndex = selectedTabIndex,
onTabSelected = { index ->
scope.launch { pagerState.animateScrollToPage(index) }
},
modifier = Modifier.weight(1f)
)
if (BookshelfConfig.shouldShowExpandButton) {
Box(modifier = Modifier.padding(end = 16.dp)) {
SmallOutlinedIconToggleButton(
@@ -363,7 +347,7 @@ fun BookshelfScreen(
) { dismiss ->
uiState.groups.forEachIndexed { index, group ->
RoundDropdownMenuItem(
text = { Text(group.groupName) },
text = group.groupName,
onClick = {
scope.launch { pagerState.animateScrollToPage(index) }
dismiss()
@@ -502,13 +486,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(
@@ -521,40 +507,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 = {}) {
@@ -568,10 +553,10 @@ fun BookshelfScreen(
) {
CircularProgressIndicator()
uiState.loadingText?.let {
Text(
AppText(
text = it,
modifier = Modifier.padding(top = 16.dp),
style = MaterialTheme.typography.bodyMedium
style = LegadoTheme.typography.bodyMedium
)
}
}
@@ -6,20 +6,14 @@ 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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -27,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
@@ -36,8 +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.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.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.move
import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -45,6 +39,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GroupManageSheet(
show: Boolean,
onDismissRequest: () -> Unit,
viewModel: GroupViewModel = koinViewModel(),
bookshelfViewModel: BookshelfViewModel = koinViewModel()
@@ -77,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 = {
@@ -92,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
) {
Text(
text = stringResource(R.string.group_manage),
style = MaterialTheme.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
}
)
}
}
}
}
@@ -42,8 +42,6 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -73,6 +71,9 @@ import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.book.search.SearchScope
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.login.SourceLoginActivity
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.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
@@ -81,9 +82,11 @@ import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.startActivity
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import top.yukonga.miuix.kmp.theme.MiuixTheme
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@@ -138,6 +141,8 @@ fun ExploreScreen(
}
}
val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
ListScaffold(
title = stringResource(R.string.discovery),
state = uiState,
@@ -148,13 +153,13 @@ fun ExploreScreen(
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { Text(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 = { Text(group) },
text = group,
onClick = { viewModel.setGroup(group); dismiss() }
)
}
@@ -197,7 +202,8 @@ fun ExploreScreen(
}
},
onRefresh = { viewModel.refreshExploreKinds(item) },
onDelete = { sourceToDelete = item }
onDelete = { sourceToDelete = item },
isMiuix = composeEngine
)
}
@@ -228,7 +234,8 @@ fun ExploreScreen(
putExtra("exploreUrl", kind.url)
}
}
}
},
isMiuix = composeEngine
)
}
@@ -264,7 +271,8 @@ fun ExploreScreen(
uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl }
if (index >= 0) listState.animateScrollToItem(index)
}
}
},
isMiuix = composeEngine
)
}
}
@@ -274,19 +282,19 @@ fun ExploreScreen(
sourceToDelete?.let { source ->
AlertDialog(
onDismissRequest = { sourceToDelete = null },
title = { Text(stringResource(R.string.draw)) },
text = { Text(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
title = { AppText(stringResource(R.string.draw)) },
text = { AppText(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
confirmButton = {
TextButton(onClick = {
viewModel.deleteSource(source)
sourceToDelete = null
}) {
Text(stringResource(R.string.yes))
AppText(stringResource(R.string.yes))
}
},
dismissButton = {
TextButton(onClick = { sourceToDelete = null }) {
Text(stringResource(R.string.no))
AppText(stringResource(R.string.no))
}
}
)
@@ -341,14 +349,25 @@ fun ExploreSourceHeader(
onLogin: () -> Unit,
onRefresh: () -> Unit,
onDelete: () -> Unit,
isMiuix: Boolean,
) {
var showMenu by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(if (isExpanded) 90f else 0f, label = "rotation")
val containerColor by animateColorAsState(
targetValue = if (isExpanded)
MaterialTheme.colorScheme.secondaryContainer
if (isMiuix) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
else
MaterialTheme.colorScheme.surfaceContainerLow,
if (isMiuix) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
label = "CardColor"
)
val contentColor by animateColorAsState(
targetValue = if (isExpanded)
if (isMiuix) MiuixTheme.colorScheme.primary else MaterialTheme.colorScheme.primary
else
if (isMiuix) MiuixTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurface,
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
label = "CardColor"
)
@@ -358,7 +377,7 @@ fun ExploreSourceHeader(
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp),
shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors(containerColor = containerColor),
containerColor = containerColor
) {
ListItem(
modifier = Modifier
@@ -371,14 +390,10 @@ fun ExploreSourceHeader(
containerColor = Color.Transparent
),
headlineContent = {
Text(
AppText(
text = item.bookSourceName,
style = MaterialTheme.typography.titleMedium,
color = if (isExpanded) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
}
style = LegadoTheme.typography.titleMedium,
color = contentColor
)
},
trailingContent = {
@@ -401,29 +416,29 @@ fun ExploreSourceHeader(
PillHeaderDivider(title = item.bookSourceName)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { Text(stringResource(R.string.to_top)) },
text = stringResource(R.string.to_top),
onClick = { onTop(); showMenu = false }
)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { Text(stringResource(R.string.edit)) },
text = stringResource(R.string.edit),
onClick = { onEdit(); showMenu = false }
)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Search) },
text = { Text(stringResource(R.string.search)) },
text = stringResource(R.string.search),
onClick = { onSearch(); showMenu = false }
)
if (item.hasLoginUrl) {
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { Text(stringResource(R.string.login)) },
text = stringResource(R.string.login),
onClick = { onLogin(); showMenu = false }
)
}
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Refresh) },
text = { Text(stringResource(R.string.refresh)) },
text = stringResource(R.string.refresh),
onClick = { onRefresh(); showMenu = false }
)
RoundDropdownMenuItem(
@@ -433,12 +448,8 @@ fun ExploreSourceHeader(
tint = MaterialTheme.colorScheme.error
)
},
text = {
Text(
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
text = stringResource(R.string.delete),
color = LegadoTheme.colorScheme.error,
onClick = { onDelete(); showMenu = false }
)
}
@@ -450,12 +461,12 @@ fun ExploreSourceHeader(
@Composable
fun ExploreStickyCard(
item: BookSourcePart,
onClick: () -> Unit
onClick: () -> Unit,
isMiuix: Boolean,
) {
TextCard(
text = item.bookSourceName,
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
textStyle = MaterialTheme.typography.labelLarge,
textStyle = LegadoTheme.typography.labelLarge,
horizontalPadding = 8.dp,
verticalPadding = 6.dp,
onClick = onClick
@@ -467,8 +478,29 @@ fun ExploreKindItem(
kind: ExploreKind,
isClickable: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
isMiuix: Boolean,
) {
val color = if (isMiuix)
MiuixTheme.colorScheme.surfaceContainer
else
MaterialTheme.colorScheme.secondaryContainer
val contentColor = if (isMiuix)
MiuixTheme.colorScheme.onSurface
else
MaterialTheme.colorScheme.secondary
val unClickBackColor = if (isMiuix)
MiuixTheme.colorScheme.surfaceContainer
else
MaterialTheme.colorScheme.surface
val unClickColor = if (isMiuix)
MiuixTheme.colorScheme.disabledOnSurface
else
MaterialTheme.colorScheme.primary
CompositionLocalProvider(
LocalMinimumInteractiveComponentSize provides Dp.Unspecified
) {
@@ -479,21 +511,19 @@ fun ExploreKindItem(
GlassCard(
onClick = onClick,
shape = shape,
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.6f),
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
),
containerColor = color.copy(alpha = 0.6f),
contentColor = contentColor,
modifier = modifier
) {
KindText(kind)
}
} else {
OutlinedCard(
GlassCard(
shape = shape,
colors = CardDefaults.outlinedCardColors(
contentColor = MaterialTheme.colorScheme.primary
),
modifier = modifier
containerColor = unClickBackColor.copy(alpha = 0.6f),
contentColor = unClickColor,
modifier = modifier,
border = CardDefaults.outlinedCardBorder()
) {
KindText(kind)
}
@@ -506,12 +536,12 @@ fun ExploreKindItem(
private fun KindText(
kind: ExploreKind
) {
Text(
AppText(
text = kind.title,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
style = MaterialTheme.typography.labelMediumEmphasized,
style = LegadoTheme.typography.labelMediumEmphasized,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1
@@ -1,13 +1,20 @@
package io.legado.app.ui.main.my
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -16,20 +23,19 @@ import androidx.compose.material.icons.automirrored.filled.HelpOutline
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
import androidx.compose.material.icons.automirrored.filled.Rule
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.FindReplace
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.OpenInBrowser
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Source
import androidx.compose.material.icons.filled.Web
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -46,11 +52,14 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
import io.legado.app.ui.dict.rule.DictRuleActivity
import io.legado.app.ui.file.FileManageActivity
import io.legado.app.ui.replace.ReplaceRuleActivity
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.settingItem.SettingItem
import io.legado.app.ui.widget.components.button.SmallTextButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -70,11 +79,7 @@ fun MyScreen(
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
topBar = {
GlassMediumFlexibleTopAppBar(
title = {
Text(
text = stringResource(R.string.my)
)
},
title = stringResource(R.string.my),
actions = {
IconButton(
onClick = { onNavigate(PrefClickEvent.ShowMd("appHelp", "xxx")) }
@@ -99,52 +104,21 @@ fun MyScreen(
)
SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp),
modifier = Modifier.adaptiveHorizontalPadding(),
title = ""
) {
SettingItem(
title = stringResource(R.string.web_service),
description = if (uiState.isWebServiceRun)
uiState.webServiceAddress
else
stringResource(R.string.web_service_desc),
imageVector = Icons.Default.Web,
trailingContent = {
Switch(
checked = uiState.isWebServiceRun,
onCheckedChange = {
viewModel.onEvent(PrefClickEvent.ToggleWebService)
}
)
},
dropdownMenu = if (uiState.isWebServiceRun) {
{ onDismiss ->
DropdownMenuItem(
text = { Text("复制地址") },
onClick = {
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
onDismiss()
}
)
DropdownMenuItem(
text = { Text("浏览器打开") },
onClick = {
onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
onDismiss()
}
)
}
} else null,
onClick = { }
WebServiceSettingBlock(
uiState = uiState,
viewModel = viewModel,
onNavigate = onNavigate
)
}
SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp),
modifier = Modifier.adaptiveHorizontalPadding(),
title = "规则"
) {
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.book_source_manage),
description = stringResource(R.string.book_source_manage_desc),
imageVector = Icons.Default.Source,
@@ -154,7 +128,7 @@ fun MyScreen(
)
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.replace_purify),
imageVector = Icons.Default.FindReplace,
onClick = {
@@ -163,7 +137,7 @@ fun MyScreen(
)
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.txt_toc_rule),
imageVector = Icons.AutoMirrored.Filled.Rule,
onClick = {
@@ -172,7 +146,7 @@ fun MyScreen(
)
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.dict_rule),
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
onClick = {
@@ -184,44 +158,44 @@ fun MyScreen(
}
SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp),
modifier = Modifier.adaptiveHorizontalPadding(),
title = stringResource(R.string.other)) {
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.setting),
imageVector = Icons.Default.Settings,
onClick = {
onOpenSettings()
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.bookmark),
imageVector = Icons.Default.Bookmark,
onClick = {
onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java))
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.read_record),
imageVector = Icons.Default.History,
onClick = {
onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.file_manage),
imageVector = Icons.Default.Folder,
onClick = {
onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java))
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.about),
imageVector = Icons.Default.Info,
onClick = {
onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java))
}
)
SettingItem(
ClickableSettingItem(
title = stringResource(R.string.exit),
imageVector = Icons.AutoMirrored.Filled.ExitToApp,
onClick = {
@@ -234,3 +208,58 @@ fun MyScreen(
}
}
}
@Composable
fun WebServiceSettingBlock(
uiState: MyUiState,
viewModel: MyViewModel,
onNavigate: (PrefClickEvent) -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
SwitchSettingItem(
title = stringResource(R.string.web_service),
description = if (uiState.isWebServiceRun) {
uiState.webServiceAddress
} else {
stringResource(R.string.web_service_desc)
},
imageVector = Icons.Default.Web,
checked = uiState.isWebServiceRun,
onCheckedChange = {
viewModel.onEvent(PrefClickEvent.ToggleWebService)
}
)
AnimatedVisibility(
visible = uiState.isWebServiceRun,
enter = expandVertically(),
exit = shrinkVertically()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.End
) {
SmallTextButton(
text = "复制地址",
icon = Icons.Default.ContentCopy,
onClick = {
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
}
)
Spacer(modifier = Modifier.width(12.dp))
SmallTextButton(
text = "浏览器打开",
icon = Icons.Default.OpenInBrowser,
onClick = {
onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
}
)
}
}
}
}
@@ -30,7 +30,6 @@ import androidx.compose.material.icons.filled.VerticalAlignTop
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -55,6 +54,7 @@ import io.legado.app.ui.rss.read.ReadRssActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.ui.rss.subscription.RuleSubActivity
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.SourceIcon
import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.divider.PillDivider
@@ -63,6 +63,7 @@ import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.openUrl
import io.legado.app.utils.startActivity
import org.koin.androidx.compose.koinViewModel
@@ -135,12 +136,12 @@ fun RssScreen(
dismiss()
},
leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
text = { Text("订阅源管理") }
text = "订阅源管理"
)
PillDivider()
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { Text(stringResource(R.string.all)) },
text = stringResource(R.string.all),
onClick = {
viewModel.setGroup("")
dismiss()
@@ -149,7 +150,7 @@ fun RssScreen(
uiState.groups.forEach { group ->
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { Text(group) },
text = group,
onClick = {
viewModel.setGroup(group)
dismiss()
@@ -188,8 +189,8 @@ fun RssScreen(
sourceToDelete?.let { source ->
AlertDialog(
onDismissRequest = { sourceToDelete = null },
title = { Text(stringResource(R.string.draw)) },
text = { Text(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
title = { AppText(stringResource(R.string.draw)) },
text = { AppText(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
confirmButton = {
TextButton(
onClick = {
@@ -197,12 +198,12 @@ fun RssScreen(
sourceToDelete = null
}
) {
Text(stringResource(R.string.yes))
AppText(stringResource(R.string.yes))
}
},
dismissButton = {
TextButton(onClick = { sourceToDelete = null }) {
Text(stringResource(R.string.no))
AppText(stringResource(R.string.no))
}
}
)
@@ -246,7 +247,7 @@ fun RssSourceGridItem(
PillHeaderDivider(title = source.sourceName)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { Text(stringResource(R.string.to_top)) },
text = stringResource(R.string.to_top),
onClick = {
onTop()
showMenu = false
@@ -254,7 +255,7 @@ fun RssSourceGridItem(
)
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { Text(stringResource(R.string.edit)) },
text = stringResource(R.string.edit),
onClick = {
onEdit()
showMenu = false
@@ -263,7 +264,7 @@ fun RssSourceGridItem(
if (!source.loginUrl.isNullOrBlank()) {
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { Text(stringResource(R.string.login)) },
text = stringResource(R.string.login),
onClick = {
onLogin()
showMenu = false
@@ -272,7 +273,7 @@ fun RssSourceGridItem(
}
RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Close) },
text = { Text(stringResource(R.string.disable_source)) },
text = stringResource(R.string.disable_source),
onClick = {
onDisable()
showMenu = false
@@ -285,12 +286,8 @@ fun RssSourceGridItem(
tint = MaterialTheme.colorScheme.error
)
},
text = {
Text(
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
text = stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error,
onClick = {
onDelete()
showMenu = false
@@ -299,9 +296,9 @@ fun RssSourceGridItem(
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
AppText(
text = source.sourceName,
style = MaterialTheme.typography.labelMedium,
style = LegadoTheme.typography.labelMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
@@ -8,11 +8,9 @@ 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
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -20,20 +18,15 @@ 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
import androidx.compose.material3.Icon
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
@@ -50,14 +43,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
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 androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -76,6 +67,8 @@ 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.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -165,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 {
Text(rule.name, style = MaterialTheme.typography.titleMedium)
if (!rule.group.isNullOrBlank()) {
Text(rule.group!!, style = MaterialTheme.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() }
@@ -267,28 +252,29 @@ 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(
onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) },
title = { AppText(stringResource(R.string.delete)) },
text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = {
OutlinedButton(onClick = {
viewModel.delete(rule); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) }
}) { AppText(stringResource(R.string.ok)) }
},
dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) {
Text(
AppText(
stringResource(R.string.cancel)
)
}
@@ -347,39 +333,21 @@ fun ReplaceRuleScreen(
},
bottomContent = {
if (tabItems.size > 1) {
PrimaryScrollableTabRow(
selectedTabIndex = selectedTabIndex.coerceAtMost(tabItems.size - 1)
.coerceAtLeast(0),
edgePadding = 0.dp,
divider = {},
containerColor = Color.Transparent,
) {
tabItems.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
onClick = {
selectedTabIndex = index
viewModel.setGroup(title)
},
text = {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
)
AppTabRow(
tabTitles = tabItems,
selectedTabIndex = selectedTabIndex,
onTabSelected = { index ->
selectedTabIndex = index
viewModel.setGroup(tabItems[index])
}
}
)
}
},
floatingActionButton = {
TooltipBox(
positionProvider =
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text("Localized description") } },
tooltip = { PlainTooltip { AppText("Localized description") } },
state = rememberTooltipState(),
) {
with(sharedTransitionScope) {
@@ -406,29 +374,29 @@ fun ReplaceRuleScreen(
snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_str)) },
text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
)
RoundDropdownMenuItem(
text = { Text("分组管理") },
text = "分组管理",
onClick = { showGroupManageSheet = true; dismiss() }
)
RoundDropdownMenuItem(
text = { Text("帮助") },
text = "帮助",
onClick = { /*TODO*/ dismiss() }
)
PillDivider()
RoundDropdownMenuItem(
text = { Text("旧的在前") },
text = "旧的在前",
onClick = { viewModel.setSortMode("asc"); dismiss() }
)
RoundDropdownMenuItem(
text = { Text("新的在前") },
text = "新的在前",
onClick = { viewModel.setSortMode("desc"); dismiss() }
)
RoundDropdownMenuItem(
text = { Text("名称升序") },
text = "名称升序",
onClick = {
viewModel.setSortMode("name_asc")
dismiss()
@@ -438,7 +406,7 @@ fun ReplaceRuleScreen(
}
)
RoundDropdownMenuItem(
text = { Text("名称降序") },
text = "名称降序",
onClick = {
viewModel.setSortMode("name_desc")
dismiss()
@@ -495,16 +463,16 @@ fun ReplaceRuleScreen(
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
),
dropdownContent = { dismiss ->
DropdownMenuItem(
text = { Text("移至顶部") },
RoundDropdownMenuItem(
text = "移至顶部",
onClick = { viewModel.toTop(ui.rule); dismiss() }
)
DropdownMenuItem(
text = { Text("移至底部") },
RoundDropdownMenuItem(
text = "移至底部",
onClick = { viewModel.toBottom(ui.rule); dismiss() }
)
DropdownMenuItem(
text = { Text("删除") },
RoundDropdownMenuItem(
text = "删除",
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
)
}
@@ -52,7 +52,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
@@ -78,10 +77,11 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.button.AlertButton
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
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
@Composable
@@ -119,7 +119,7 @@ fun ReplaceEditScreen(
.imePadding(),
topBar = {
GlassMediumFlexibleTopAppBar(
title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") },
title = if (state.id > 0) "编辑替换规则" else "新增替换规则",
navigationIcon = {
TopbarNavigationButton(onClick = onBack)
},
@@ -143,14 +143,14 @@ fun ReplaceEditScreen(
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
text = { Text("复制规则") },
text = { AppText("复制规则") },
onClick = {
showMenu = false
viewModel.copyRule()
}
)
DropdownMenuItem(
text = { Text("粘贴规则") },
text = { AppText("粘贴规则") },
onClick = {
showMenu = false
viewModel.pasteRule(onSuccess = {})
@@ -167,7 +167,7 @@ fun ReplaceEditScreen(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Above
),
tooltip = { PlainTooltip { Text("添加") } },
tooltip = { PlainTooltip { AppText("添加") } },
state = rememberTooltipState(),
) {
FloatingActionButton(
@@ -214,7 +214,7 @@ fun ReplaceEditScreen(
OutlinedTextField(
value = state.name,
onValueChange = viewModel::onNameChange,
label = { Text("规则名称") },
label = { AppText("规则名称") },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged {
@@ -234,8 +234,8 @@ fun ReplaceEditScreen(
OutlinedTextField(
value = state.pattern,
onValueChange = viewModel::onPatternChange,
label = { Text("匹配规则") },
placeholder = { Text("输入正则表达式或关键字") },
label = { AppText("匹配规则") },
placeholder = { AppText("输入正则表达式或关键字") },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged {
@@ -247,8 +247,8 @@ fun ReplaceEditScreen(
OutlinedTextField(
value = state.replacement,
onValueChange = viewModel::onReplacementChange,
label = { Text("替换为") },
placeholder = { Text("输入替换内容或捕获组") },
label = { AppText("替换为") },
placeholder = { AppText("输入替换内容或捕获组") },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged {
@@ -265,7 +265,7 @@ fun ReplaceEditScreen(
FilterChip(
selected = state.scopeTitle,
onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
label = { Text("标题") },
label = { AppText("标题") },
leadingIcon = if (state.scopeTitle) {
{
Icon(
@@ -282,7 +282,7 @@ fun ReplaceEditScreen(
FilterChip(
selected = state.scopeContent,
onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
label = { Text("内容") },
label = { AppText("内容") },
leadingIcon = if (state.scopeContent) {
{
Icon(
@@ -299,7 +299,7 @@ fun ReplaceEditScreen(
FilterChip(
selected = state.isRegex,
onClick = { viewModel.onRegexChange(!state.isRegex) },
label = { Text("使用正则") },
label = { AppText("使用正则") },
leadingIcon = if (state.isRegex) {
{
Icon(
@@ -316,8 +316,8 @@ fun ReplaceEditScreen(
OutlinedTextField(
value = state.scope,
onValueChange = viewModel::onScopeChange,
label = { Text("特定范围") },
placeholder = { Text("指定规则适用的范围") },
label = { AppText("特定范围") },
placeholder = { AppText("指定规则适用的范围") },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged {
@@ -329,8 +329,8 @@ fun ReplaceEditScreen(
OutlinedTextField(
value = state.excludeScope,
onValueChange = viewModel::onExcludeScopeChange,
label = { Text("排除范围") },
placeholder = { Text("指定规则不适用的范围") },
label = { AppText("排除范围") },
placeholder = { AppText("指定规则不适用的范围") },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged {
@@ -342,8 +342,8 @@ fun ReplaceEditScreen(
OutlinedTextField(
value = state.timeout,
onValueChange = viewModel::onTimeoutChange,
label = { Text("超时 (ms)") },
placeholder = { Text("3000") },
label = { AppText("超时 (ms)") },
placeholder = { AppText("3000") },
modifier = Modifier.fillMaxWidth()
)
@@ -383,8 +383,8 @@ fun GroupSelector(
OutlinedTextField(
value = currentGroup,
onValueChange = onGroupChange,
label = { Text("分组") },
placeholder = { Text("默认") },
label = { AppText("分组") },
placeholder = { AppText("默认") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier
@@ -400,7 +400,7 @@ fun GroupSelector(
) {
allGroups.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
text = { AppText(selectionOption) },
onClick = {
onGroupChange(selectionOption)
expanded = false
@@ -426,9 +426,9 @@ fun ManageGroupDialog(
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("分组管理") },
title = { AppText("分组管理") },
text = {
if (groups.isEmpty()) Text("暂无其他分组")
if (groups.isEmpty()) AppText("暂无其他分组")
else Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -448,7 +448,7 @@ fun ManageGroupDialog(
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = isSelected, onCheckedChange = null)
Text(group, Modifier.padding(start = 8.dp))
AppText(group, Modifier.padding(start = 8.dp))
}
}
}
@@ -462,7 +462,7 @@ fun ManageGroupDialog(
text = "删除选中"
)
},
dismissButton = { TextButton(onClick = onDismiss) { Text("关闭") } }
dismissButton = { TextButton(onClick = onDismiss) { AppText("关闭") } }
)
}
@@ -482,7 +482,7 @@ fun QuickInputBar(
symbols.forEach { symbol ->
AssistChip(
onClick = { onInsert(symbol) },
label = { Text(symbol) },
label = { AppText(symbol) },
modifier = Modifier.padding(end = 8.dp)
)
}
@@ -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
@@ -19,7 +17,6 @@ import androidx.compose.material.icons.filled.Group
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -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))
Text(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))
Text(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))
Text(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))
Text(stringResource(R.string.all))
}
},
onClick = {
@@ -248,14 +241,14 @@ fun RssFavoritesScreen(
},
dropdownContent = { dismiss ->
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.change_group)) },
text = stringResource(R.string.change_group),
onClick = {
showSetGroupDialog = rssStar
dismiss()
}
)
RoundDropdownMenuItem(
text = { Text(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
@@ -17,11 +16,9 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -51,13 +48,13 @@ 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
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.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -175,46 +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 {
Text(source.sourceName, style = MaterialTheme.typography.titleMedium)
Text(source.sourceUrl, style = MaterialTheme.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) {
@@ -225,16 +217,16 @@ fun RssSourceScreen(
showDeleteRuleDialog?.let { source ->
AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) },
title = { AppText(stringResource(R.string.delete)) },
text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = {
OutlinedButton(onClick = {
viewModel.del(source); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) }
}) { AppText(stringResource(R.string.ok)) }
},
dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -287,11 +279,11 @@ fun RssSourceScreen(
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
onClick = { showGroupManageSheet = true },
text = { Text("分组管理") },
text = "分组管理",
)
Box {
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_rss_source)) },
text = stringResource(R.string.import_rss_source),
onClick = { showImportMenu = true }
)
RoundDropdownMenu(
@@ -299,7 +291,7 @@ fun RssSourceScreen(
onDismissRequest = { showImportMenu = false }
) {
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_on_line)) },
text = stringResource(R.string.import_on_line),
onClick = {
showImportMenu = false
dismiss()
@@ -307,7 +299,7 @@ fun RssSourceScreen(
}
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_local)) },
text = stringResource(R.string.import_local),
onClick = {
showImportMenu = false
dismiss()
@@ -315,7 +307,7 @@ fun RssSourceScreen(
}
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_default_rule)) },
text = stringResource(R.string.import_default_rule),
onClick = {
showImportMenu = false
dismiss()
@@ -326,29 +318,29 @@ fun RssSourceScreen(
}
PillDivider()
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.all)) },
text = stringResource(R.string.all),
onClick = { dismiss(); viewModel.setGroupFilter(null) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.enabled)) },
text = stringResource(R.string.enabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.disabled)) },
text = stringResource(R.string.disabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) }
)
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.need_login)) },
text = stringResource(R.string.need_login),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) }
)
RoundDropdownMenuItem(
text = { Text(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 = { Text(group) },
text = group,
onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$group") }
)
}
@@ -22,11 +22,9 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
@@ -54,12 +52,14 @@ import io.legado.app.data.entities.RuleSubType
import io.legado.app.ui.association.ImportBookSourceDialog
import io.legado.app.ui.association.ImportReplaceRuleDialog
import io.legado.app.ui.association.ImportRssSourceDialog
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
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.showDialogFragment
import io.legado.app.utils.toastOnUi
@@ -89,11 +89,12 @@ 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))
Text(stringResource(R.string.sort))
AppText(stringResource(R.string.sort))
}
},
onClick = {
@@ -106,7 +107,7 @@ fun RuleSubScreen(
TooltipBox(
positionProvider =
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text("Localized description") } },
tooltip = { PlainTooltip { AppText("Localized description") } },
state = rememberTooltipState(),
) {
FloatingActionButton(
@@ -185,7 +186,7 @@ fun RuleSubScreen(
},
dropdownContent = { dismiss ->
RoundDropdownMenuItem(
text = { Text(stringResource(R.string.delete)) },
text = stringResource(R.string.delete),
onClick = {
viewModel.delete(ruleSub)
dismiss()
@@ -226,24 +227,24 @@ fun RuleSubEditDialog(
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.rule_subscription)) },
title = { AppText(stringResource(R.string.rule_subscription)) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text(stringResource(R.string.name)) },
label = { AppText(stringResource(R.string.name)) },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = url,
onValueChange = { url = it },
label = { Text("URL") },
label = { AppText("URL") },
modifier = Modifier.fillMaxWidth()
)
Text(
AppText(
text = "订阅类型",
style = MaterialTheme.typography.titleSmall,
style = LegadoTheme.typography.titleSmall,
modifier = Modifier.padding(top = 8.dp)
)
CheckboxGroupContainer(columns = 2) {
@@ -265,12 +266,12 @@ fun RuleSubEditDialog(
TextButton(onClick = {
onConfirm(ruleSub.copy(name = name, url = url, type = type))
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
}
)
@@ -0,0 +1,19 @@
package io.legado.app.ui.theme
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme.composeEngine
@Composable
fun Modifier.adaptiveHorizontalPadding(): Modifier {
val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp
return this.padding(horizontal = horizontal)
}
@Composable
fun Modifier.adaptiveVerticalPadding(): Modifier {
val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 8.dp
return this.padding(horizontal = horizontal)
}
@@ -7,9 +7,14 @@ import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
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
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -20,29 +25,274 @@ fun AppTheme(
val context = LocalContext.current
val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme)
val isPureBlack = ThemeConfig.isPureBlack
val paletteStyle = ThemeConfig.paletteStyle
val paletteStyleStr = ThemeConfig.paletteStyle
val materialVersion = ThemeConfig.materialVersion
val composeEngine = ThemeConfig.composeEngine
val colorSchemeMode = ThemeResolver.resolveColorSchemeMode(ThemeConfig.themeMode)
val paletteStyle =
remember(paletteStyleStr) { ThemeResolver.resolvePaletteStyle(paletteStyleStr) }
val seedColor = remember(ThemeConfig.cPrimary) {
if (ThemeConfig.cPrimary != 0) Color(ThemeConfig.cPrimary) else Color(0xFF3482FF)
}
val colorScheme =
remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle, materialVersion) {
remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyleStr, materialVersion) {
ThemeManager.getColorScheme(
context = context,
mode = appThemeMode,
darkTheme = darkTheme,
isAmoled = isPureBlack,
paletteStyle = paletteStyle,
paletteStyle = paletteStyleStr,
materialVersion = materialVersion
)
}
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
val themeColors = remember(
colorScheme,
darkTheme,
seedColor,
paletteStyle,
colorSchemeMode,
composeEngine
) {
AppBackground(darkTheme = darkTheme) {
content()
LegadoThemeMode(
colorScheme = colorScheme,
isDark = darkTheme,
seedColor = seedColor,
paletteStyle = paletteStyle,
themeMode = colorSchemeMode,
useDynamicColor = appThemeMode == AppThemeMode.Dynamic,
composeEngine = composeEngine
)
}
CompositionLocalProvider(
LocalLegadoThemeColors provides themeColors
) {
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)
)
}
val miuixColorScheme = MiuixTheme.colorScheme
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,
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() }
}
}
}
}
}
}
@@ -7,8 +7,8 @@ import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.hazeStyle.HazeLegado
/**
* 自动感知全局配置的 HazeSource
@@ -31,9 +31,9 @@ fun Modifier.responsiveHazeEffect(
if (!enableBlur) return this
val style = if (enableProgressiveBlur) {
HazeMaterials.ultraThin()
HazeLegado.ultraThin()
} else {
HazeMaterials.regular()
HazeLegado.regular()
}
return this.hazeEffect(
@@ -61,6 +61,6 @@ fun Modifier.regularHazeEffect(state: HazeState): Modifier {
return this.hazeEffect(
state = state,
style = HazeMaterials.ultraThin()
style = HazeLegado.ultraThin()
)
}
@@ -0,0 +1,172 @@
package io.legado.app.ui.theme
import androidx.compose.material3.ColorScheme
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 LegadoThemeMode(
val colorScheme: ColorScheme,
val isDark: Boolean,
val seedColor: Color,
val paletteStyle: PaletteStyle,
val themeMode: ColorSchemeMode,
val useDynamicColor: Boolean,
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 {
LegadoThemeMode(
colorScheme = lightColorScheme(),
isDark = false,
seedColor = Color.Unspecified,
paletteStyle = PaletteStyle.TonalSpot,
themeMode = ColorSchemeMode.System,
useDynamicColor = true,
composeEngine = "material"
)
}
val LocalHazeState = compositionLocalOf<HazeState?> { null }
object LegadoTheme {
val colorScheme: LegadoColorScheme
@Composable
@ReadOnlyComposable
get() = LocalLegadoColorScheme.current
val isDark: Boolean
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.isDark
val seedColor: Color
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.seedColor
val paletteStyle: PaletteStyle
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.paletteStyle
val themeMode: ColorSchemeMode
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.themeMode
val composeEngine: String
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.composeEngine
val useDynamicColor: Boolean
@Composable
@ReadOnlyComposable
get() = LocalLegadoThemeColors.current.useDynamicColor
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
}
}
}
@@ -4,6 +4,7 @@ import com.materialkolor.Contrast
import com.materialkolor.PaletteStyle
import com.materialkolor.dynamiccolor.ColorSpec
import io.legado.app.ui.config.themeConfig.ThemeConfig
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
object ThemeResolver {
@@ -48,6 +49,17 @@ object ThemeResolver {
}
}
fun resolveColorSchemeMode(value: String): ColorSchemeMode = when (value) {
"0" -> ColorSchemeMode.System
"1" -> ColorSchemeMode.Light
"2" -> ColorSchemeMode.Dark
else -> ColorSchemeMode.System
}
fun isMiuixEngine(composeEngine: String): Boolean = composeEngine == "miuix"
fun isM3Engine(composeEngine: String): Boolean = composeEngine == "m3"
fun resolveColorSpecVersion(colorSpec: ThemeColorSpec): ColorSpec.SpecVersion {
return when (colorSpec) {
ThemeColorSpec.SPEC_2025 -> ColorSpec.SpecVersion.SPEC_2025
@@ -55,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
}
}
}
@@ -0,0 +1,44 @@
package io.legado.app.ui.theme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Typography
import androidx.compose.ui.text.font.FontWeight
import top.yukonga.miuix.kmp.theme.TextStyles
/**
* 将 Miuix 的 TextStyles 语义化映射为 Material 3 的 Typography
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun miuixStylesToM3Typography(miuixStyles: TextStyles): Typography {
return Typography(
displayLarge = miuixStyles.title1, // 32.sp
displayMedium = miuixStyles.title2, // 24.sp
displaySmall = miuixStyles.title3, // 20.sp
headlineLarge = miuixStyles.title2, // 24.sp
headlineMedium = miuixStyles.title3, // 20.sp
headlineSmall = miuixStyles.title4, // 18.sp
titleLarge = miuixStyles.headline1, // 17.sp
titleMedium = miuixStyles.headline2, // 16.sp
titleSmall = miuixStyles.subtitle, // 14.sp, Bold
bodyLarge = miuixStyles.paragraph, // 17.sp, lineHeight 1.2em (如果不需要行高也可以换成 main)
bodyMedium = miuixStyles.body1, // 16.sp
bodySmall = miuixStyles.body2, // 14.sp
labelLarge = miuixStyles.button, // 17.sp
labelMedium = miuixStyles.footnote1, // 13.sp
labelSmall = miuixStyles.footnote2, // 11.sp
bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium),
labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium),
labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
)
}
@@ -0,0 +1,48 @@
package io.legado.app.ui.theme.hazeStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.dp
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.HazeTint
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
object HazeLegado {
@Composable
@ReadOnlyComposable
fun ultraThin(
containerColor: Color = if (ThemeResolver.isMiuixEngine(composeEngine)) MiuixTheme.colorScheme.surface else MaterialTheme.colorScheme.surface,
): HazeStyle = hazeLegado(
containerColor = containerColor,
lightAlpha = 0.35f,
darkAlpha = 0.55f,
)
@Composable
@ReadOnlyComposable
fun regular(
containerColor: Color = if (ThemeResolver.isMiuixEngine(composeEngine)) MiuixTheme.colorScheme.surface else MaterialTheme.colorScheme.surface,
): HazeStyle = hazeLegado(
containerColor = containerColor,
lightAlpha = 0.73f,
darkAlpha = 0.8f,
)
private fun hazeLegado(
containerColor: Color,
lightAlpha: Float,
darkAlpha: Float,
): HazeStyle = HazeStyle(
blurRadius = 24.dp,
backgroundColor = containerColor,
tint = HazeTint(
containerColor.copy(alpha = if (containerColor.luminance() >= 0.5) lightAlpha else darkAlpha),
),
)
}
@@ -8,12 +8,9 @@ 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
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -22,25 +19,27 @@ import androidx.compose.ui.draw.rotate
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(
@@ -54,20 +53,20 @@ fun CollapsibleHeader(
if (titleContent != null) {
titleContent()
} else {
Text(
AppText(
text = title,
style = MaterialTheme.typography.bodySmallEmphasized.copy(
style = LegadoTheme.typography.bodySmallEmphasized.copy(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
color = LegadoTheme.colorScheme.primary
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
subtitle?.let {
Text(
AppText(
text = it,
style = MaterialTheme.typography.labelSmallEmphasized.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant
style = LegadoTheme.typography.labelSmallEmphasized.copy(
color = LegadoTheme.colorScheme.onSurfaceVariant
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@@ -81,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
)
}
}
}
}
@@ -0,0 +1,56 @@
package io.legado.app.ui.widget.components
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.FloatingActionButton as MiuixFloatingActionButton
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppFloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
tooltipText: String? = null,
content: @Composable () -> Unit
) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
MiuixFloatingActionButton(
onClick = onClick,
modifier = modifier,
content = content
)
} else {
if (tooltipText != null) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Above
),
tooltip = { PlainTooltip { AppText(tooltipText) } },
state = rememberTooltipState(),
) {
FloatingActionButton(
onClick = onClick,
modifier = modifier,
content = content
)
}
} else {
FloatingActionButton(
onClick = onClick,
modifier = modifier,
content = content
)
}
}
}
@@ -0,0 +1,93 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.RowScope
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
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 top.yukonga.miuix.kmp.basic.NavigationBarDisplayMode
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.NavigationBar as MiuixNavigationBar
import top.yukonga.miuix.kmp.basic.NavigationBarItem as MiuixNavigationBarItem
@Composable
fun AppNavigationBar(
modifier: Modifier = Modifier,
miuixMode: NavigationBarDisplayMode = NavigationBarDisplayMode.IconAndText,
content: @Composable RowScope.() -> Unit
) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val opacity = (ThemeConfig.bottomBarOpacity.coerceIn(0, 100)) / 100f
if (isMiuix) {
val baseColor = GlassDefaults.glassColor(
noBlurColor = MiuixTheme.colorScheme.surface,
blurAlpha = GlassDefaults.DefaultBlurAlpha
)
val finalColor = baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
MiuixNavigationBar(
modifier = modifier,
color = finalColor,
mode = miuixMode,
content = content
)
} else {
val baseColor = GlassDefaults.glassColor(
noBlurColor = BottomAppBarDefaults.containerColor,
blurAlpha = GlassDefaults.DefaultBlurAlpha
)
val finalColor = baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
NavigationBar(
modifier = modifier,
containerColor = finalColor,
content = content
)
}
}
@Composable
fun RowScope.AppNavigationBarItem(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
labelString: String,
miuixIcon: ImageVector,
m3Icon: @Composable () -> Unit,
m3IndicatorColor: Color,
m3ShowLabel: Boolean,
m3AlwaysShowLabel: Boolean,
) {
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
if (isMiuix) {
MiuixNavigationBarItem(
selected = selected,
onClick = onClick,
icon = miuixIcon,
label = labelString,
modifier = modifier
)
} else {
NavigationBarItem(
selected = selected,
onClick = onClick,
modifier = modifier,
icon = m3Icon,
colors = NavigationBarItemDefaults.colors(indicatorColor = m3IndicatorColor),
label = if (m3ShowLabel) {
{ Text(labelString) }
} else null,
alwaysShowLabel = m3AlwaysShowLabel
)
}
}
@@ -3,89 +3,113 @@ package io.legado.app.ui.widget.components
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import dev.chrisbanes.haze.HazeState
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.responsiveHazeEffect
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.responsiveHazeSource
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.FabPosition as MiuixFabPosition
import top.yukonga.miuix.kmp.basic.Scaffold as MiuixScaffold
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppScaffold(
modifier: Modifier = Modifier,
title: (@Composable () -> Unit)? = null,
subtitle: (@Composable () -> Unit)? = null,
scrollBehavior: TopAppBarScrollBehavior? = null,
navigationIcon: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {},
colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors(),
topBar: @Composable () -> Unit = {},
topBar: @Composable (HazeState) -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
contentColor: Color = contentColorFor(MaterialTheme.colorScheme.background),
contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit
) {
val isDark = isSystemInDarkTheme()
val hasImageBg = ThemeConfig.hasImageBg(isDark)
val hazeState = remember { HazeState() }
val composeEngine = LegadoTheme.composeEngine
val containerColor = if (hasImageBg) {
Color.Transparent
} else {
MaterialTheme.colorScheme.background
LegadoTheme.colorScheme.background
}
val finalTopBar = @Composable {
Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) {
if (title != null) {
GlassMediumFlexibleTopAppBar(
title = title,
subtitle = subtitle,
scrollBehavior = scrollBehavior,
navigationIcon = navigationIcon,
actions = actions,
colors = colors
)
} else {
topBar()
val miuixContainerColor = if (hasImageBg) {
Color.Transparent
} else {
MiuixTheme.colorScheme.surface
}
CompositionLocalProvider(
LocalHazeState provides if (ThemeConfig.enableBlur) hazeState else null
) {
when {
ThemeResolver.isMiuixEngine(composeEngine) -> {
val miuixFabPosition = when (floatingActionButtonPosition) {
FabPosition.End -> MiuixFabPosition.End
FabPosition.Center -> MiuixFabPosition.Center
else -> MiuixFabPosition.End
}
MiuixScaffold(
modifier = modifier,
topBar = {
topBar(hazeState)
},
bottomBar = bottomBar,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = miuixFabPosition,
containerColor = miuixContainerColor,
contentWindowInsets = contentWindowInsets
) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.responsiveHazeSource(hazeState)
) {
content(paddingValues)
}
}
}
else -> {
Scaffold(
modifier = modifier,
topBar = {
topBar(hazeState)
},
bottomBar = bottomBar,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor,
contentColor = contentColor,
contentWindowInsets = contentWindowInsets
) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.responsiveHazeSource(hazeState)
) {
content(paddingValues)
}
}
}
}
}
Scaffold(
modifier = modifier,
topBar = finalTopBar,
bottomBar = bottomBar,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor,
contentColor = contentColor,
contentWindowInsets = contentWindowInsets
) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.responsiveHazeSource(hazeState)
) {
content(paddingValues)
}
}
}
@@ -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
)
}
}
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.ContainedLoadingIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -24,6 +23,8 @@ import androidx.compose.ui.text.style.TextAlign
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
@@ -70,7 +71,7 @@ fun EmptyMessageView(
AnimatedTextLine(
text = message,
style = MaterialTheme.typography.labelLarge,
style = LegadoTheme.typography.labelLarge,
textAlign = TextAlign.Center,
maxLines = 2,
softWrap = true,
@@ -1,113 +0,0 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumFlexibleTopAppBar
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import io.legado.app.ui.config.themeConfig.ThemeConfig
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun GlassMediumFlexibleTopAppBar(
modifier: Modifier = Modifier,
title: @Composable () -> Unit,
subtitle: (@Composable () -> Unit)? = null,
scrollBehavior: TopAppBarScrollBehavior? = null,
navigationIcon: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {},
colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors()
) {
if (ThemeConfig.useFlexibleTopAppBar) {
MediumFlexibleTopAppBar(
modifier = modifier,
title = title,
subtitle = subtitle,
navigationIcon = navigationIcon,
actions = actions,
scrollBehavior = scrollBehavior,
colors = colors
)
} else {
TopAppBar(
modifier = modifier,
title = title,
navigationIcon = navigationIcon,
actions = actions,
scrollBehavior = scrollBehavior,
colors = colors
)
}
}
object GlassTopAppBarDefaults {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun defaultScrollBehavior(): TopAppBarScrollBehavior {
return if (ThemeConfig.useFlexibleTopAppBar) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
} else {
TopAppBarDefaults.pinnedScrollBehavior()
}
}
@Composable
fun glassColors(): TopAppBarColors {
val containerColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.surface,
blurAlpha = GlassDefaults.TransparentAlpha
)
val scrolledContainerColor = if (ThemeConfig.enableBlur) {
MaterialTheme.colorScheme.surface.copy(alpha = GlassDefaults.TransparentAlpha)
} else {
MaterialTheme.colorScheme.surfaceContainer
}
return TopAppBarDefaults.topAppBarColors(
containerColor = applyTopBarOpacity(containerColor),
scrolledContainerColor = applyTopBarOpacity(scrolledContainerColor)
)
}
@Composable
fun containerColor(): Color {
val baseColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.surface,
blurAlpha = GlassDefaults.TransparentAlpha
)
return applyTopBarOpacity(baseColor)
}
@Composable
fun scrolledContainerColor(): Color {
val baseColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.surfaceContainer,
blurAlpha = GlassDefaults.TransparentAlpha
)
return applyTopBarOpacity(baseColor)
}
@Composable
fun controlContainerColor(): Color {
val baseColor = GlassDefaults.glassColor(
noBlurColor = MaterialTheme.colorScheme.surfaceContainerHighest,
blurAlpha = GlassDefaults.DefaultBlurAlpha
)
return applyTopBarOpacity(baseColor)
}
private fun applyTopBarOpacity(color: Color): Color {
val opacity = (ThemeConfig.topBarOpacity.coerceIn(0, 100)) / 100f
return color.copy(alpha = (color.alpha * opacity).coerceIn(0f, 1f))
}
}
@@ -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
@@ -18,7 +16,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -26,45 +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.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,
) {
Text(
modifier = Modifier.padding(bottom = 16.dp),
text = stringResource(R.string.group_manage),
style = MaterialTheme.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
)
}
}
}
@@ -115,7 +103,7 @@ private fun GroupItem(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 48.dp),
label = { Text(stringResource(R.string.edit)) },
label = { AppText(stringResource(R.string.edit)) },
contentPadding = PaddingValues(
top = 4.dp,
bottom = 4.dp,
@@ -10,6 +10,40 @@ import androidx.compose.material3.SwitchDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Switch as MiuixSwitch
@Composable
fun AdaptiveSwitch(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
checkedIcon: ImageVector = Icons.Filled.Check,
uncheckedIcon: ImageVector? = null,
showIcon: Boolean = true
) {
val composeEngine = LegadoTheme.composeEngine
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixSwitch(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled
)
} else {
IconSwitch(
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled,
checkedIcon = checkedIcon,
uncheckedIcon = uncheckedIcon,
showIcon = showIcon
)
}
}
@Composable
fun IconSwitch(
@@ -1,33 +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.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -38,13 +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),
placeholder = { Text(placeholder) },
.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()
}
}
}
@@ -15,7 +15,6 @@ import androidx.compose.material3.HorizontalFloatingToolbar
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
@@ -27,6 +26,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.legado.app.ui.widget.components.text.AppText
data class SelectionActions(
val primaryAction: ActionItem,
@@ -84,7 +84,7 @@ fun SelectionBottomBar(
) {
secondaryActions.forEach { action ->
DropdownMenuItem(
text = { Text(action.text) },
text = { AppText(action.text) },
leadingIcon = action.icon,
onClick = {
action.onClick()
@@ -101,7 +101,7 @@ fun SelectionBottomBar(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Above
),
tooltip = { PlainTooltip { Text(primaryAction.text) } },
tooltip = { PlainTooltip { AppText(primaryAction.text) } },
state = rememberTooltipState(),
) {
FilledIconButton(
@@ -7,14 +7,14 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.draw.clip
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.title.AdaptiveTitle
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
/**
* Settings Group Container by https://github.com/wxxsfxyzm/InstallerX-Revived
@@ -29,24 +29,33 @@ fun SplicedColumnGroup(
title: String = "",
content: @Composable ColumnScope.() -> Unit,
) {
Column(modifier = modifier.padding(top = 8.dp, bottom = 16.dp)) {
val composeEngine = LegadoTheme.composeEngine
Column(modifier = modifier.padding(top = 8.dp, bottom = 8.dp)) {
if (title.isNotEmpty()) {
Text(
AdaptiveTitle(
text = title,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 16.dp, bottom = 8.dp)
)
}
Card(
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixCard {
Column(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.clip(RoundedCornerShape(16.dp))
) {
content()
}
}
} else {
Column(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.clip(RoundedCornerShape(16.dp)),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
content()
@@ -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,15 +7,11 @@ 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.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -26,11 +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.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,
@@ -41,25 +41,20 @@ 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()
) {
Text(
text = bookmark.chapterName,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField(
value = bookText,
onValueChange = { bookText = it },
label = { Text("原文") },
label = { AppText("原文") },
modifier = Modifier.fillMaxWidth(),
maxLines = 10
)
@@ -69,7 +64,7 @@ fun BookmarkEditSheet(
OutlinedTextField(
value = content,
onValueChange = { content = it },
label = { Text("摘要/笔记") },
label = { AppText("摘要/笔记") },
modifier = Modifier.fillMaxWidth(),
maxLines = 5
)
@@ -81,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)
) {
Text("删除")
}
modifier = Modifier.weight(1f),
text = "删除"
)
Button(
PrimaryButton(
onClick = {
val newBookmark = bookmark.apply {
this.bookText = bookText
@@ -97,10 +90,9 @@ fun BookmarkEditSheet(
}
onSave(newBookmark)
},
modifier = Modifier.weight(1f)
) {
Text("保存")
}
modifier = Modifier.weight(1f),
text = "保存"
)
}
Spacer(modifier = Modifier.height(16.dp))
}
@@ -109,8 +101,8 @@ fun BookmarkEditSheet(
if (showDeleteConfirmDialog) {
AlertDialog(
onDismissRequest = { showDeleteConfirmDialog = false },
title = { Text("确认删除") },
text = { Text("你确定要删除这条书签吗?") },
title = { AppText("确认删除") },
text = { AppText("你确定要删除这条书签吗?") },
confirmButton = {
TextButton(
onClick = {
@@ -119,14 +111,14 @@ fun BookmarkEditSheet(
},
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
) {
Text("删除")
AppText("删除")
}
},
dismissButton = {
TextButton(
onClick = { showDeleteConfirmDialog = false }
) {
Text("取消")
AppText("取消")
}
}
)
@@ -9,9 +9,7 @@ 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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@@ -19,6 +17,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
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.text.AppText
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -30,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"
)
@@ -49,29 +49,29 @@ fun BookmarkItem(
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
) {
Text(
AppText(
text = bookmark.chapterName,
style = MaterialTheme.typography.titleSmallEmphasized,
color = if (isDur) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary
style = LegadoTheme.typography.titleSmallEmphasized,
color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.secondary
)
if (bookmark.bookText.isNotEmpty()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
AppText(
text = bookmark.bookText,
style = MaterialTheme.typography.bodyMedium,
style = LegadoTheme.typography.bodyMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurfaceVariant
color = LegadoTheme.colorScheme.onSurfaceVariant
)
}
if (bookmark.content.isNotEmpty()) {
Spacer(modifier = Modifier.height(2.dp))
Text(
AppText(
text = bookmark.content,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
style = LegadoTheme.typography.bodyMedium,
color = LegadoTheme.colorScheme.primary
)
}
}
@@ -3,10 +3,10 @@ package io.legado.app.ui.widget.components.button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import io.legado.app.ui.widget.components.text.AppText
@Composable
fun AlertButton(
@@ -24,6 +24,6 @@ fun AlertButton(
containerColor = Color.Transparent,
)
) {
Text(text = text)
AppText(text = text)
}
}
@@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -21,6 +19,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -73,9 +73,9 @@ fun AnimatedActionButton(
AnimatedVisibility(
visible = showText
) {
Text(
AppText(
text = if (lastCheckedState) activeText else inactiveText,
style = MaterialTheme.typography.labelMedium,
style = LegadoTheme.typography.labelMedium,
modifier = Modifier.padding(start = 8.dp),
maxLines = 1,
softWrap = false
@@ -8,11 +8,11 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.material3.ContainedLoadingIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -38,7 +38,7 @@ fun AnimatedTextButton(
if (loading) {
ContainedLoadingIndicator()
} else {
Text(text)
AppText(text)
}
}
}
@@ -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
)
}
}
@@ -0,0 +1,57 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.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 MiuixIconButton(
modifier: Modifier = Modifier,
imageVector: ImageVector = MiuixIcons.Regular.Back,
tint: Color = LocalContentColor.current,
contentDescription: String? = stringResource(id = R.string.back),
onClick: () -> Unit,
) {
MiuixIconButton(
onClick = onClick,
modifier = modifier
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
tint = tint
)
}
}
@Composable
fun MiuixToggleButton(
modifier: Modifier = Modifier,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
iconChecked: ImageVector,
iconUnchecked: ImageVector = iconChecked,
checkedTint: Color = MiuixTheme.colorScheme.primary,
uncheckedTint: Color = LocalContentColor.current,
contentDescription: String? = null,
) {
MiuixIconButton(
onClick = { onCheckedChange(!checked) },
modifier = modifier
) {
Icon(
imageVector = if (checked) iconChecked else iconUnchecked,
contentDescription = contentDescription,
tint = if (checked) checkedTint else uncheckedTint
)
}
}
@@ -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
@@ -15,11 +17,9 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconToggleButtonShapes
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.OutlinedToggleButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -31,7 +31,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.delay
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -41,22 +47,33 @@ fun SmallIconButton(
icon: ImageVector,
contentDescription: String? = null
) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
IconButton(
onClick = onClick,
modifier = Modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shape = IconButtonDefaults.extraSmallRoundShape,
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onClick
) {
Icon(
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
contentDescription = contentDescription
)
}
} else {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
IconButton(
onClick = onClick,
modifier = Modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shape = IconButtonDefaults.extraSmallRoundShape,
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
)
}
}
}
}
@@ -65,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,
@@ -87,6 +120,7 @@ fun SmallOutlinedIconButton(
)
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -94,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)
)
}
}
}
}
@@ -127,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),
)
}
}
}
}
@@ -204,9 +285,9 @@ fun SmallAnimatedActionButton(
AnimatedVisibility(
visible = showText
) {
Text(
AppText(
text = if (lastCheckedState) activeText else inactiveText,
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
modifier = Modifier.padding(start = 6.dp),
maxLines = 1,
softWrap = false
@@ -6,12 +6,18 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import 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(
@@ -20,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))
Text(
text = text,
style = MaterialTheme.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
)
}
}
}
@@ -22,7 +22,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -35,7 +34,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
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
enum class TopBarButtonVariant {
@@ -105,17 +108,26 @@ 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
) {
TopBarButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier.padding(horizontal = 12.dp),
style = style
)
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixIconButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier.padding(horizontal = 16.dp)
)
} else {
TopBarButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier.padding(horizontal = 12.dp),
style = style
)
}
}
@Composable
@@ -126,21 +138,29 @@ fun TopBarActionButton(
modifier: Modifier = Modifier
) {
val enableProgressive = ThemeConfig.enableProgressiveBlur
if (enableProgressive) {
TopBarButton(
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixIconButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier.padding(end = 12.dp),
style = TopBarButtonVariant.Filled
modifier = modifier.padding(end = 8.dp),
)
} else {
IconButton(onClick = onClick) {
Icon(
if (enableProgressive) {
TopBarButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription
contentDescription = contentDescription,
modifier = modifier.padding(end = 12.dp),
style = TopBarButtonVariant.Filled
)
} else {
IconButton(onClick = onClick) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription
)
}
}
}
}
@@ -165,43 +185,51 @@ fun TopBarAnimatedActionButton(
showText = false
}
}
ToggleButton(
modifier = modifier.height(36.dp),
contentPadding = PaddingValues(horizontal = 8.dp),
checked = checked,
onCheckedChange = {
lastCheckedState = it
onCheckedChange(it)
showText = true
}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
AnimatedContent(
targetState = checked,
label = "IconAnimation"
) { targetChecked ->
AnimatedIcon(
modifier = Modifier.size(20.dp),
imageVector = if (targetChecked) iconChecked else iconUnchecked,
contentDescription = null
)
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
MiuixToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
iconChecked = iconChecked,
iconUnchecked = iconUnchecked
)
} else {
ToggleButton(
modifier = modifier.height(36.dp),
contentPadding = PaddingValues(horizontal = 8.dp),
checked = checked,
onCheckedChange = {
lastCheckedState = it
onCheckedChange(it)
showText = true
}
AnimatedVisibility(
visible = showText
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = if (lastCheckedState) activeText else inactiveText,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(start = 8.dp),
maxLines = 1,
softWrap = false
)
AnimatedContent(
targetState = checked,
label = "IconAnimation"
) { targetChecked ->
AnimatedIcon(
modifier = Modifier.size(20.dp),
imageVector = if (targetChecked) iconChecked else iconUnchecked,
contentDescription = null
)
}
AnimatedVisibility(
visible = showText
) {
AppText(
text = if (lastCheckedState) activeText else inactiveText,
style = LegadoTheme.typography.labelMedium,
modifier = Modifier.padding(start = 8.dp),
maxLines = 1,
softWrap = false
)
}
}
}
}
@@ -4,14 +4,79 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation
import androidx.compose.material3.MaterialTheme
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.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun BaseCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape,
cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
containerColor: Color? = null,
contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(),
border: BorderStroke? = null,
alpha: Float = 1f,
content: @Composable ColumnScope.() -> Unit
) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
val colors = MiuixCardDefaults.defaultColors(
color = (containerColor
?: MiuixTheme.colorScheme.secondaryContainer).copy(alpha = alpha),
contentColor = contentColor ?: MiuixTheme.colorScheme.onSurface
)
MiuixCard(
modifier = modifier,
cornerRadius = cornerRadius,
onClick = onClick,
content = content,
colors = colors
)
} else {
val colors = CardDefaults.cardColors(
containerColor = (containerColor ?: MaterialTheme.colorScheme.secondaryContainer).copy(
alpha = alpha
),
contentColor = contentColor ?: MaterialTheme.colorScheme.onSecondaryContainer,
disabledContainerColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f),
disabledContentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f)
)
if (onClick != null) {
Card(
modifier = modifier,
shape = shape,
colors = colors,
elevation = elevation,
border = border,
onClick = onClick,
content = content
)
} else {
Card(
modifier = modifier,
shape = shape,
colors = colors,
elevation = elevation,
border = border,
content = content
)
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
@@ -19,43 +84,50 @@ fun GlassCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape,
colors: CardColors? = null,
cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
containerColor: Color? = null,
contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(),
border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit
) {
val containerAlpha = ThemeConfig.containerOpacity / 100f
BaseCard(
modifier = modifier,
onClick = onClick,
shape = shape,
cornerRadius = cornerRadius,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
border = border,
alpha = ThemeConfig.containerOpacity / 100f,
content = content
)
}
val baseColors = colors ?: CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NormalCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape,
cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
containerColor: Color? = null,
contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(),
border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit
) {
BaseCard(
modifier = modifier,
onClick = onClick,
shape = shape,
cornerRadius = cornerRadius,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
border = border,
alpha = 1f,
content = content
)
val finalColors = CardDefaults.cardColors(
containerColor = baseColors.containerColor.copy(alpha = containerAlpha),
contentColor = baseColors.contentColor,
disabledContainerColor = baseColors.disabledContainerColor.copy(alpha = containerAlpha),
disabledContentColor = baseColors.disabledContentColor
)
if (onClick != null) {
Card(
onClick = onClick,
modifier = modifier,
shape = shape,
colors = finalColors,
elevation = elevation,
border = border,
content = content
)
} else {
Card(
modifier = modifier,
shape = shape,
colors = finalColors,
elevation = elevation,
border = border,
content = content
)
}
}
@@ -24,8 +24,6 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -41,10 +39,17 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.AdaptiveSwitch
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.text.AppText
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.ReorderableLazyListState
import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable
fun SelectionItemCard(
@@ -66,11 +71,14 @@ fun SelectionItemCard(
) {
var showMenu by remember { mutableStateOf(false) }
val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
val animatedContainerColor by animateColorAsState(
targetValue = if (isSelected)
selectedContainerColor ?: MaterialTheme.colorScheme.secondaryContainer
selectedContainerColor
?: if (composeEngine) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
else
containerColor ?: MaterialTheme.colorScheme.surfaceContainerLow,
containerColor
?: if (composeEngine) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
label = "CardColor"
)
@@ -78,10 +86,9 @@ fun SelectionItemCard(
GlassCard(
onClick = onToggleSelection,
modifier = modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors(containerColor = animatedContainerColor),
containerColor = animatedContainerColor,
elevation = CardDefaults.cardElevation(defaultElevation = elevation)
) {
Row(
@@ -113,28 +120,49 @@ fun SelectionItemCard(
}
}
ListItem(
modifier = Modifier.weight(1f),
headlineContent = {
Text(
if (composeEngine) {
BasicComponent(
modifier = Modifier.weight(1f)
) {
AppText(
text = title,
style = MaterialTheme.typography.titleSmall,
style = LegadoTheme.typography.titleSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
supportingContent = if (!subtitle.isNullOrBlank()) {
{
Text(
if (!subtitle.isNullOrBlank()) {
AppText(
text = subtitle,
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
} else null,
colors = ListItemDefaults.colors(containerColor = Color.Transparent)
)
}
} else {
ListItem(
modifier = Modifier.weight(1f),
headlineContent = {
AppText(
text = title,
style = LegadoTheme.typography.titleSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
supportingContent = if (!subtitle.isNullOrBlank()) {
{
AppText(
text = subtitle,
style = LegadoTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
} else null,
colors = ListItemDefaults.colors(containerColor = Color.Transparent)
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -143,7 +171,7 @@ fun SelectionItemCard(
.padding(end = 8.dp)
) {
onEnabledChange?.let {
Switch(
AdaptiveSwitch(
modifier = Modifier.scale(0.8f),
checked = isEnabled,
onCheckedChange = it
@@ -0,0 +1,75 @@
package io.legado.app.ui.widget.components.card
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.BasicComponent
// import top.yukonga.miuix.kmp.basic.theme.LocalColors as MiuixLocalColors
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SettingCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape,
colors: CardColors? = null,
elevation: CardElevation = CardDefaults.cardElevation(),
border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit
) {
val composeEngine = LegadoTheme.composeEngine
val containerAlpha = ThemeConfig.containerOpacity / 100f
if (ThemeResolver.isMiuixEngine(composeEngine)) {
BasicComponent(
modifier = modifier,
onClick = onClick,
content = content
)
} else {
val baseColors = colors ?: CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
val finalColors = CardDefaults.cardColors(
containerColor = baseColors.containerColor.copy(alpha = containerAlpha),
contentColor = baseColors.contentColor,
disabledContainerColor = baseColors.disabledContainerColor.copy(alpha = containerAlpha),
disabledContentColor = baseColors.disabledContentColor
)
if (onClick != null) {
Card(
onClick = onClick,
modifier = modifier,
shape = shape,
colors = finalColors,
elevation = elevation,
border = border,
content = content
)
} else {
Card(
modifier = modifier,
shape = shape,
colors = finalColors,
elevation = elevation,
border = border,
content = content
)
}
}
}
@@ -6,50 +6,47 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
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.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.legado.app.ui.widget.components.AnimatedTextLine
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@Preview(showBackground = true)
@Composable
fun PreviewTextCard() {
MaterialTheme {
TextCard(
text = "v1.0.0"
)
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun TextCard(
modifier: Modifier = Modifier,
text: String,
icon: ImageVector? = null,
onClick: (() -> Unit)? = null,
backgroundColor: Color = colorScheme.primaryContainer,
contentColor: Color = colorScheme.onPrimaryContainer,
backgroundColor: Color? = null,
contentColor: Color? = null,
cornerRadius: Dp = 8.dp,
horizontalPadding: Dp = 8.dp,
verticalPadding: Dp = 2.dp,
iconSize: Dp = 14.dp,
spacing: Dp = 4.dp,
textStyle: TextStyle = MaterialTheme.typography.labelSmall
textStyle: TextStyle = LegadoTheme.typography.labelSmallEmphasized
) {
GlassCard(
val defaultBackground = LegadoTheme.colorScheme.surfaceContainer
val defaultContent = LegadoTheme.colorScheme.onSurface
val finalBackgroundColor = backgroundColor ?: defaultBackground
val finalContentColor = contentColor ?: defaultContent
NormalCard(
modifier = modifier,
shape = RoundedCornerShape(cornerRadius),
colors = CardDefaults.cardColors(containerColor = backgroundColor),
containerColor = finalBackgroundColor,
contentColor = finalContentColor,
onClick = onClick
) {
Row(
@@ -61,10 +58,10 @@ fun TextCard(
) {
if (icon != null) {
Icon(
AppIcon(
imageVector = icon,
contentDescription = null,
tint = contentColor,
tint = finalContentColor,
modifier = Modifier.size(iconSize)
)
Spacer(modifier = Modifier.width(spacing))
@@ -73,7 +70,7 @@ fun TextCard(
AnimatedTextLine(
text = text,
style = textStyle,
color = contentColor
color = finalContentColor
)
}
}
@@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import io.legado.app.ui.widget.components.text.AppText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -15,6 +15,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
@Composable
fun CheckboxItem(
@@ -45,9 +46,9 @@ fun CheckboxItem(
enabled = enabled,
modifier = Modifier.alpha(alpha)
)
Text(
AppText(
text = title,
style = MaterialTheme.typography.bodyMedium,
style = LegadoTheme.typography.bodyMedium,
maxLines = 1,
modifier = Modifier
.padding(start = 12.dp)
@@ -7,12 +7,12 @@ 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.widget.components.card.TextCard
@Composable
@@ -44,8 +44,6 @@ fun BookshelfCover(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(2.dp),
backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
cornerRadius = 4.dp,
horizontalPadding = 4.dp,
verticalPadding = 0.dp
@@ -59,8 +57,8 @@ fun BookshelfCover(
.fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 6.dp)
.height(3.dp),
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
color = LegadoTheme.colorScheme.primary,
trackColor = LegadoTheme.colorScheme.primary.copy(alpha = 0.2f),
strokeCap = StrokeCap.Round
)
}
@@ -22,7 +22,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -41,11 +40,14 @@ import com.github.skydoves.colorpicker.compose.BrightnessSlider
import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
import io.legado.app.R
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
import io.legado.app.ui.theme.LegadoTheme
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
@@ -79,16 +81,16 @@ fun ColorPickerSheet(
)
}
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
AppText(
text = "颜色选择",
style = MaterialTheme.typography.titleLarge,
style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
@@ -168,9 +170,9 @@ fun ColorPickerSheet(
)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
AppText(
text = "#${Integer.toHexString(currentColor.toArgb()).uppercase()}",
style = MaterialTheme.typography.bodyLarge
style = LegadoTheme.typography.bodyLarge
)
Row(
@@ -178,14 +180,14 @@ fun ColorPickerSheet(
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel))
AppText(stringResource(R.string.cancel))
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
onColorSelected(currentColor.toArgb())
onDismissRequest()
}) {
Text(stringResource(R.string.ok))
AppText(stringResource(R.string.ok))
}
}
}
@@ -10,9 +10,7 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -22,6 +20,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AppText
@Composable
fun TextListInputDialog(
@@ -36,22 +36,22 @@ fun TextListInputDialog(
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(title) },
title = { AppText(title) },
text = {
Column {
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text(hint) },
label = { AppText(hint) },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
if (suggestions.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Text(
AppText(
text = "建议:",
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
modifier = Modifier.padding(bottom = 4.dp)
)
LazyRow(
@@ -61,7 +61,7 @@ fun TextListInputDialog(
items(suggestions) { suggestion ->
AssistChip(
onClick = { text = suggestion },
label = { Text(suggestion) }
label = { AppText(suggestion) }
)
}
}
@@ -72,12 +72,12 @@ fun TextListInputDialog(
TextButton(
onClick = { onConfirm(text) }
) {
Text(stringResource(android.R.string.ok))
AppText(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
}
)
@@ -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,8 +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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -17,14 +15,16 @@ 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.widget.components.text.AppText
@Composable
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
@@ -40,9 +40,9 @@ fun PillHeaderDivider(
.background(dotColor)
)
Text(
AppText(
text = title,
style = MaterialTheme.typography.labelSmall,
style = LegadoTheme.typography.labelSmall,
color = titleColor,
modifier = Modifier.padding(horizontal = 12.dp),
maxLines = 1
@@ -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,10 +16,8 @@ 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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -28,7 +25,10 @@ import androidx.compose.ui.graphics.vector.ImageVector
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.theme.LegadoTheme
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 {
DIR, FILE, EXPORT
@@ -37,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,
@@ -46,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
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 20.dp)
)
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -126,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(
@@ -134,16 +130,16 @@ 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))
Text(
AppText(
text = text,
style = MaterialTheme.typography.labelMedium,
style = LegadoTheme.typography.labelMedium,
maxLines = 1
)
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -41,7 +40,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.AnimatedActionButton
import io.legado.app.ui.widget.components.text.AppText
import java.time.LocalDate
/**
@@ -60,7 +61,7 @@ fun HeatmapCalendarTopBar(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("时间线", style = MaterialTheme.typography.titleMedium)
AppText("时间线", style = LegadoTheme.typography.titleMedium)
Row(verticalAlignment = Alignment.CenterVertically) {
AnimatedActionButton(
@@ -107,7 +108,7 @@ fun WeekdayLabelsColumn(
labels.forEachIndexed { index, label ->
if (index % 2 == 0) {
Text(
AppText(
text = label,
fontSize = 10.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -170,7 +171,7 @@ fun NoEarlierDataIndicator(
verticalArrangement = Arrangement.Center
) {
"没有更早数据".forEach { char ->
Text(
AppText(
text = char.toString(),
fontSize = 9.sp,
lineHeight = 12.sp,
@@ -252,7 +253,7 @@ fun HeatmapWeekColumn(
// 月份标签
if (firstDayOfMonth != null) {
Text(
AppText(
text = "${firstDayOfMonth.monthValue}",
fontSize = 10.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -280,9 +281,9 @@ fun HeatmapLegend(
) {
val legendUnit = if (mode == HeatmapMode.COUNT) "" else ""
Text(
AppText(
"少($legendUnit)",
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = Color.Gray
)
@@ -298,9 +299,9 @@ fun HeatmapLegend(
Spacer(modifier = Modifier.width(4.dp))
}
Text(
AppText(
"多($legendUnit)",
style = MaterialTheme.typography.bodySmall,
style = LegadoTheme.typography.bodySmall,
color = Color.Gray
)
}
@@ -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,26 +22,18 @@ 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
import androidx.compose.material3.OutlinedToggleButton
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
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 +47,13 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.widget.components.AnimatedText
import io.legado.app.ui.theme.LegadoTheme
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
@Composable
@@ -77,25 +69,25 @@ fun SourceInputDialog(
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(title) },
title = { AppText(title) },
text = {
Column {
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text(hint) },
label = { AppText(hint) },
modifier = Modifier.fillMaxWidth(),
maxLines = 5
)
if (historyValues.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Text("历史记录:", style = MaterialTheme.typography.labelSmall)
AppText("历史记录:", style = LegadoTheme.typography.labelSmall)
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
items(historyValues) { history ->
AssistChip(
onClick = { text = history },
label = { Text(history, maxLines = 1) }
label = { AppText(history, maxLines = 1) }
)
}
}
@@ -105,41 +97,52 @@ fun SourceInputDialog(
confirmButton = {
OutlinedButton(
onClick = { if (text.isNotBlank()) onConfirm(text) }
) { Text(stringResource(android.R.string.ok)) }
) { AppText(stringResource(android.R.string.ok)) }
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(android.R.string.cancel))
AppText(stringResource(android.R.string.cancel))
}
}
)
}
//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
@@ -170,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) }
}
)
}
}
@@ -215,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 = {
Text(
text = when (status) {
ImportStatus.New -> "新增"
ImportStatus.Update -> "更新"
ImportStatus.Existing -> "已有"
ImportStatus.Error -> "错误"
},
style = MaterialTheme.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)
@@ -315,12 +281,12 @@ fun ImportBottomBar(
)
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedButton(onClick = onCancel) { Text("取消") }
OutlinedButton(onClick = onCancel) { AppText("取消") }
Button(
enabled = selectedCount > 0,
onClick = onConfirm
) {
Text("导入")
AppText("导入")
}
}
}
@@ -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,
@@ -15,19 +15,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -35,11 +27,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SelectionActions
import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarScrollBehavior
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -52,28 +46,21 @@ fun <T> ListScaffold(
onSearchQueryChange: (String) -> Unit,
searchPlaceholder: String = "搜索...",
topBarActions: @Composable RowScope.() -> Unit = {},
bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null,
bottomContent: @Composable (ColumnScope.(GlassTopAppBarScrollBehavior) -> Unit)? = null,
dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
selectionActions: SelectionActions? = null,
onAddClick: (() -> Unit)? = null,
floatingActionButton: @Composable () -> Unit = {
onAddClick?.let { onClick ->
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Above
AppFloatingActionButton(
onClick = onClick,
modifier = Modifier.animateFloatingActionButton(
visible = state.selectedIds.isEmpty(),
alignment = Alignment.BottomEnd,
),
tooltip = { PlainTooltip { Text("添加") } },
state = rememberTooltipState(),
tooltipText = "添加"
) {
FloatingActionButton(
modifier = Modifier.animateFloatingActionButton(
visible = state.selectedIds.isEmpty(),
alignment = Alignment.BottomEnd,
),
onClick = onClick
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
},
@@ -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,53 +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 MaterialTheme.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
@@ -66,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) {
@@ -93,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
)
}
}
}
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -36,7 +35,8 @@ 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()) {
@@ -84,7 +86,7 @@ fun <T> RuleEditSheet(
.verticalScroll(rememberScrollState())
) {
CenterAlignedTopAppBar(
title = { Text(title) },
title = { AppText(title) },
navigationIcon = {
IconButton(onClick = onDismissRequest) {
Icon(
@@ -99,7 +101,7 @@ fun <T> RuleEditSheet(
}
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem(
text = { Text(stringResource(R.string.copy_rule)) },
text = { AppText(stringResource(R.string.copy_rule)) },
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
onClick = {
onCopy(getCurrentEntity())
@@ -107,7 +109,7 @@ fun <T> RuleEditSheet(
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.paste_rule)) },
text = { AppText(stringResource(R.string.paste_rule)) },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = {
scope.launch {
@@ -136,20 +138,20 @@ fun <T> RuleEditSheet(
modifier = Modifier.fillMaxWidth(),
value = name,
onValueChange = { name = it },
label = { Text(stringResource(R.string.name)) },
label = { AppText(stringResource(R.string.name)) },
singleLine = true
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = rule1,
onValueChange = { rule1 = it },
label = { Text(label1) }
label = { AppText(label1) }
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = rule2,
onValueChange = { rule2 = it },
label = { Text(label2) },
label = { AppText(label2) },
minLines = 3
)
}

Some files were not shown because too many files have changed in this diff Show More