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.material.kolor)
implementation(libs.haze.core) implementation(libs.haze.core)
implementation(libs.haze.materials) 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, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -113,6 +114,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -141,6 +143,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -169,6 +172,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -202,6 +206,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -236,6 +241,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -265,6 +271,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -293,6 +300,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -321,6 +329,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -350,6 +359,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -378,6 +388,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -406,6 +417,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -434,6 +446,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -462,6 +475,7 @@ interface BookDao {
durChapterPos, durChapterPos,
latestChapterTitle, latestChapterTitle,
latestChapterTime, latestChapterTime,
lastCheckCount,
totalChapterNum, totalChapterNum,
durChapterIndex, durChapterIndex,
type, type,
@@ -15,9 +15,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -31,13 +28,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.appInfo 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.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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.card.TextCard 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.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) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -59,7 +58,7 @@ fun AboutScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.about)) }, title = stringResource(R.string.about),
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBack) TopbarNavigationButton(onClick = onBack)
}, },
@@ -81,9 +80,9 @@ fun AboutScreen(
.width(160.dp) .width(160.dp)
.align(Alignment.CenterHorizontally) .align(Alignment.CenterHorizontally)
) )
Text( AppText(
text = stringResource(R.string.app_name), text = stringResource(R.string.app_name),
style = MaterialTheme.typography.bodyLarge, style = LegadoTheme.typography.bodyLarge,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier modifier = Modifier
@@ -91,16 +90,14 @@ fun AboutScreen(
.wrapContentWidth(Alignment.CenterHorizontally)) .wrapContentWidth(Alignment.CenterHorizontally))
TextCard( TextCard(
text = versionName, text = versionName,
backgroundColor = colorScheme.tertiaryContainer,
contentColor = colorScheme.onTertiaryContainer,
cornerRadius = 8.dp, cornerRadius = 8.dp,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally) .wrapContentWidth(Alignment.CenterHorizontally)
.padding(vertical = 4.dp)) .padding(vertical = 4.dp))
Text( AppText(
text = stringResource(R.string.about_description), text = stringResource(R.string.about_description),
style = MaterialTheme.typography.bodyLarge, style = LegadoTheme.typography.bodyLarge,
fontSize = 12.sp, fontSize = 12.sp,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -18,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -31,19 +30,23 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppLog 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 io.legado.app.utils.LogUtils
import java.util.Date import java.util.Date
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AppLogSheet( fun AppLogSheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
var logs by remember { mutableStateOf(AppLog.logs) } var logs by remember { mutableStateOf(AppLog.logs) }
var showStackTrace by remember { mutableStateOf<String?>(null) } var showStackTrace by remember { mutableStateOf<String?>(null) }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Row( Row(
@@ -52,9 +55,9 @@ fun AppLogSheet(
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( AppText(
text = stringResource(R.string.log), text = stringResource(R.string.log),
style = MaterialTheme.typography.titleLarge, style = LegadoTheme.typography.titleLarge,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
IconButton(onClick = { IconButton(onClick = {
@@ -82,17 +85,17 @@ fun AppLogSheet(
showStackTrace?.let { stackTrace -> showStackTrace?.let { stackTrace ->
AlertDialog( AlertDialog(
onDismissRequest = { showStackTrace = null }, onDismissRequest = { showStackTrace = null },
title = { Text("Log") }, title = { AppText("Log") },
text = { text = {
SelectionContainer { SelectionContainer {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(text = stackTrace, style = MaterialTheme.typography.bodySmall) AppText(text = stackTrace, style = LegadoTheme.typography.bodySmall)
} }
} }
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { showStackTrace = null }) { 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) .clickable(onClick = onClick)
.padding(16.dp) .padding(16.dp)
) { ) {
Text( AppText(
text = LogUtils.logTimeFormat.format(Date(item.first)), text = LogUtils.logTimeFormat.format(Date(item.first)),
style = MaterialTheme.typography.labelMedium, style = LegadoTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline color = MaterialTheme.colorScheme.outline
) )
Text( AppText(
text = item.second, text = item.second,
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 4.dp) modifier = Modifier.padding(top = 4.dp)
) )
} }
@@ -26,7 +26,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue 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.CollapsibleHeader
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SearchBarSection 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.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem 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.lazylist.Scroller
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
@@ -98,11 +97,7 @@ fun AllBookmarkScreen(
topBar = { topBar = {
Column { Column {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = "所有书签",
Text(
text = "所有书签"
)
},
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBack) TopbarNavigationButton(onClick = onBack)
@@ -132,7 +127,7 @@ fun AllBookmarkScreen(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("导出 JSON") }, text = "导出 JSON",
onClick = { onClick = {
showMenu = false showMenu = false
pendingExportIsMd = false pendingExportIsMd = false
@@ -140,7 +135,7 @@ fun AllBookmarkScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("导出 Markdown") }, text = "导出 Markdown",
onClick = { onClick = {
showMenu = false showMenu = false
pendingExportIsMd = true pendingExportIsMd = true
@@ -242,22 +237,21 @@ fun AllBookmarkScreen(
} }
} }
if (showBottomSheet && editingBookmark != null) { BookmarkEditSheet(
BookmarkEditSheet( show = showBottomSheet && editingBookmark != null,
bookmark = editingBookmark!!, bookmark = editingBookmark ?: Bookmark(),
onDismiss = { onDismiss = {
showBottomSheet = false showBottomSheet = false
editingBookmark = null editingBookmark = null
}, },
onSave = { updatedBookmark -> onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark) viewModel.updateBookmark(updatedBookmark)
showBottomSheet = false showBottomSheet = false
}, },
onDelete = { bookmarkToDelete -> onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete) viewModel.deleteBookmark(bookmarkToDelete)
showBottomSheet = false showBottomSheet = false
} }
) )
}
} }
} }
@@ -52,7 +52,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState 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.SearchBook
import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.model.BookShelfState 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.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource 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.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.SearchBarSection
import io.legado.app.ui.widget.components.button.AnimatedTextButton import io.legado.app.ui.widget.components.button.AnimatedTextButton
import io.legado.app.ui.widget.components.button.TopBarActionButton 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.cover.Cover
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.androidx.compose.koinViewModel 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 modifier = Modifier
.padding(16.dp), .fillMaxWidth()
onDismissRequest = { showGridCountSheet = false } .padding(bottom = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) { ) {
Row( AppText(
modifier = Modifier text = "布局列数",
.fillMaxWidth() style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
.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)
) )
Spacer(Modifier.height(24.dp)) Spacer(modifier = Modifier.width(12.dp))
OutlinedButton( TextCard(
onClick = { showGridCountSheet = false }, text = "$gridColumnCount",
modifier = Modifier textStyle = LegadoTheme.typography.titleSmall,
.fillMaxWidth() verticalPadding = 4.dp,
.padding(horizontal = 16.dp) horizontalPadding = 12.dp,
) { cornerRadius = 12.dp
Text("完成") )
} }
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( var kindQuery by remember { mutableStateOf("") }
query = kindQuery,
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
onQueryChange = { kindQuery = it },
placeholder = "选择或搜索分类",
)
val filteredKinds = remember(kindQuery, kinds) { SearchBarSection(
if (kindQuery.isBlank()) kinds query = kindQuery,
else kinds.filter { kind -> backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
kind.title.contains(kindQuery, ignoreCase = true) || onQueryChange = { kindQuery = it },
(kind.url?.contains(kindQuery, ignoreCase = true) == true) 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( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.weight(1f, fill = false) modifier = Modifier.weight(1f, fill = false)
) { ) {
itemsIndexed( itemsIndexed(
items = filteredKinds, items = filteredKinds,
key = { index, kind -> "${kind.url ?: kind.title}_$index" }, key = { index, kind -> "${kind.url ?: kind.title}_$index" },
span = { _, kind -> span = { _, kind ->
val isClickable = !kind.url.isNullOrBlank() val isClickable = !kind.url.isNullOrBlank()
if (isClickable) GridItemSpan(1) else GridItemSpan(3) 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( AppScaffold(
modifier = Modifier modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -294,14 +293,7 @@ fun ExploreShowScreen(
modifier = Modifier.responsiveHazeEffect( modifier = Modifier.responsiveHazeEffect(
state = hazeState state = hazeState
), ),
title = { title = selectedTitle ?: title,
AnimatedTextLine(
selectedTitle ?: title,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
colors = GlassTopAppBarDefaults.glassColors(),
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBack) TopbarNavigationButton(onClick = onBack)
}, },
@@ -346,7 +338,7 @@ fun ExploreShowScreen(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("全部显示") }, text = "全部显示",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.SHOW_ALL) viewModel.setFilterState(BookFilterState.SHOW_ALL)
showMenu = false showMenu = false
@@ -358,7 +350,7 @@ fun ExploreShowScreen(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("隐藏已在书架的同源书籍") }, text = "隐藏已在书架的同源书籍",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF) viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
showMenu = false showMenu = false
@@ -370,7 +362,7 @@ fun ExploreShowScreen(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("隐藏已在书架的非同源书籍") }, text = "隐藏已在书架的非同源书籍",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR) viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
showMenu = false showMenu = false
@@ -382,7 +374,7 @@ fun ExploreShowScreen(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("只显示不在书架的书籍") }, text = "只显示不在书架的书籍",
onClick = { onClick = {
viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY) viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
showMenu = false showMenu = false
@@ -509,7 +501,7 @@ fun KindGridItem(
enabled = isClickable, enabled = isClickable,
selected = isSelected, selected = isSelected,
label = { label = {
Text( AppText(
text = kind.title, text = kind.title,
fontSize = 12.sp, fontSize = 12.sp,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -576,32 +568,32 @@ fun ExploreBookItem(
.weight(1f) .weight(1f)
.align(Alignment.CenterVertically)) { .align(Alignment.CenterVertically)) {
Text( AppText(
text = book.name, text = book.name,
style = MaterialTheme.typography.titleSmall, style = LegadoTheme.typography.titleSmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Row { Row {
Text( AppText(
text = book.author, text = book.author,
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
maxLines = 1 maxLines = 1
) )
val latestChapter = book.latestChapterTitle val latestChapter = book.latestChapterTitle
if (!latestChapter.isNullOrEmpty()) { if (!latestChapter.isNullOrEmpty()) {
Text( AppText(
text = "", text = "",
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = Color.Gray, color = Color.Gray,
maxLines = 1 maxLines = 1
) )
Text( AppText(
text = "最新: $latestChapter", text = "最新: $latestChapter",
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -612,9 +604,9 @@ fun ExploreBookItem(
val intro = book.intro?.replace("\\s+".toRegex(), "") ?: "" val intro = book.intro?.replace("\\s+".toRegex(), "") ?: ""
if (intro.isNotEmpty()) { if (intro.isNotEmpty()) {
Text( AppText(
text = intro, text = intro,
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
color = Color.Gray, color = Color.Gray,
maxLines = 2, maxLines = 2,
minLines = 2, minLines = 2,
@@ -654,9 +646,12 @@ fun ExploreBookGridItem(
val content: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) { val content: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) {
{ {
Text( AppText(
text = badgeText, 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 { } else {
@@ -681,9 +676,9 @@ fun ExploreBookGridItem(
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( AppText(
text = book.name, text = book.name,
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -698,10 +693,10 @@ fun TagChip(text: String) {
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f), color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) { ) {
Text( AppText(
text = text, text = text,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )
} }
@@ -742,13 +737,13 @@ fun LoadMoreFooter(
}, },
label = "FooterTextChange" label = "FooterTextChange"
) { text -> ) { text ->
Text( AppText(
text = text, text = text,
color = when { color = when {
errorMsg != null -> Color.Red errorMsg != null -> Color.Red
else -> Color.Gray 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.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@@ -18,11 +17,9 @@ import androidx.compose.material.icons.filled.Restore
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf 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.data.entities.BookGroup
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import io.legado.app.utils.SelectImageContract import io.legado.app.utils.SelectImageContract
@@ -55,11 +53,12 @@ import java.io.FileOutputStream
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupEditSheet( fun GroupEditSheet(
show: Boolean,
group: BookGroup? = null, group: BookGroup? = null,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: GroupViewModel = koinViewModel() viewModel: GroupViewModel = koinViewModel()
) { ) {
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
GroupEditContent( GroupEditContent(
group = group, group = group,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
@@ -108,17 +107,9 @@ fun GroupEditContent(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 16.dp)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text(
text = stringResource(R.string.group_edit),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp)
)
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
@@ -138,7 +129,7 @@ fun GroupEditContent(
OutlinedTextField( OutlinedTextField(
value = groupName, value = groupName,
onValueChange = { groupName = it }, onValueChange = { groupName = it },
label = { Text(stringResource(R.string.group_name)) }, label = { AppText(stringResource(R.string.group_name)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true singleLine = true
) )
@@ -206,7 +197,7 @@ fun GroupEditContent(
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedButton(onClick = onDismissRequest) { OutlinedButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
Button(onClick = { Button(onClick = {
if (groupName.isEmpty()) { 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.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState 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.data.entities.Server
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.model.remote.RemoteBook 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.ActionItem
import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.SelectionActions 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.card.TextCard
import io.legado.app.ui.widget.components.list.ListScaffold import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.ConvertUtils import io.legado.app.utils.ConvertUtils
import io.legado.app.utils.FileDoc import io.legado.app.utils.FileDoc
@@ -157,19 +158,19 @@ fun RemoteBookScreen(
is RemoteBookDialog.DownloadArchive -> { is RemoteBookDialog.DownloadArchive -> {
AlertDialog( AlertDialog(
onDismissRequest = { dialogState = null }, onDismissRequest = { dialogState = null },
title = { Text(stringResource(R.string.draw)) }, title = { AppText(stringResource(R.string.draw)) },
text = { Text(stringResource(R.string.archive_not_found)) }, text = { AppText(stringResource(R.string.archive_not_found)) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) } scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
dialogState = null dialogState = null
}) { }) {
Text(stringResource(android.R.string.ok)) AppText(stringResource(android.R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { dialogState = null }) { 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 -> { is RemoteBookDialog.ReImport -> {
AlertDialog( AlertDialog(
onDismissRequest = { dialogState = null }, onDismissRequest = { dialogState = null },
title = { Text("是否重新加入书架?") }, title = { AppText("是否重新加入书架?") },
text = { Text("将会覆盖书籍") }, text = { AppText("将会覆盖书籍") },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) } scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
dialogState = null dialogState = null
}) { }) {
Text(stringResource(android.R.string.ok)) AppText(stringResource(android.R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { dialogState = null }) { 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) { AppModalBottomSheet(
GlassModalBottomSheet( show = showSheet != null,
onDismissRequest = { showSheet = null } onDismissRequest = { showSheet = null }
) { ) {
when (val state = showSheet) { when (val state = showSheet) {
is RemoteBookSheet.Servers -> { is RemoteBookSheet.Servers -> {
ServersSheetContent( ServersSheetContent(
servers = uiState.servers, servers = uiState.servers,
selectedServerId = uiState.selectedServerId, selectedServerId = uiState.selectedServerId,
onSelect = { onSelect = {
viewModel.selectServer(it) viewModel.selectServer(it)
showSheet = null showSheet = null
}, },
onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) }, onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
onDelete = { viewModel.deleteServer(it) }, onDelete = { viewModel.deleteServer(it) },
onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) }, onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
onDefault = { onDefault = {
viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID) viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
showSheet = null showSheet = null
} }
) )
}
is RemoteBookSheet.ServerConfig -> {
ServerConfigSheetContent(
server = state.server,
onSave = {
viewModel.saveServer(it)
showSheet = RemoteBookSheet.Servers
},
onCancel = { showSheet = RemoteBookSheet.Servers }
)
}
else -> {}
} }
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 -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("按名称排序") }, text = "按名称排序",
onClick = { onClick = {
viewModel.toggleSort(RemoteBookSort.Name) viewModel.toggleSort(RemoteBookSort.Name)
dismiss() dismiss()
@@ -271,7 +271,7 @@ fun RemoteBookScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("按时间排序") }, text = "按时间排序",
onClick = { onClick = {
viewModel.toggleSort(RemoteBookSort.Default) viewModel.toggleSort(RemoteBookSort.Default)
dismiss() dismiss()
@@ -381,9 +381,9 @@ private fun ServersSheetContent(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text( AppText(
text = stringResource(R.string.server_config), text = stringResource(R.string.server_config),
style = MaterialTheme.typography.titleLarge style = LegadoTheme.typography.titleLarge
) )
IconButton(onClick = onAdd) { IconButton(onClick = onAdd) {
Icon(Icons.Default.Add, contentDescription = "添加") Icon(Icons.Default.Add, contentDescription = "添加")
@@ -447,9 +447,9 @@ private fun ServerItem(
ListItem( ListItem(
modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize(),
headlineContent = { headlineContent = {
Text( AppText(
text = name, text = name,
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -462,9 +462,9 @@ private fun ServerItem(
}, },
supportingContent = url?.let { supportingContent = url?.let {
{ {
Text( AppText(
text = it, text = it,
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -514,16 +514,16 @@ private fun ServerConfigSheetContent(
.padding(16.dp) .padding(16.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
Text( AppText(
text = if (server == null) "添加服务器" else "编辑服务器", text = if (server == null) "添加服务器" else "编辑服务器",
style = MaterialTheme.typography.titleLarge, style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp) modifier = Modifier.padding(bottom = 16.dp)
) )
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { Text("名称") }, label = { AppText("名称") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true singleLine = true
) )
@@ -531,7 +531,7 @@ private fun ServerConfigSheetContent(
OutlinedTextField( OutlinedTextField(
value = url, value = url,
onValueChange = { url = it }, onValueChange = { url = it },
label = { Text("URL") }, label = { AppText("URL") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true singleLine = true
) )
@@ -539,7 +539,7 @@ private fun ServerConfigSheetContent(
OutlinedTextField( OutlinedTextField(
value = username, value = username,
onValueChange = { username = it }, onValueChange = { username = it },
label = { Text("用户名") }, label = { AppText("用户名") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true singleLine = true
) )
@@ -547,7 +547,7 @@ private fun ServerConfigSheetContent(
OutlinedTextField( OutlinedTextField(
value = password, value = password,
onValueChange = { password = it }, onValueChange = { password = it },
label = { Text("密码") }, label = { AppText("密码") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
@@ -571,7 +571,7 @@ private fun ServerConfigSheetContent(
onClick = onCancel, onClick = onCancel,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
Text(stringResource(android.R.string.cancel)) AppText(stringResource(android.R.string.cancel))
} }
Button( Button(
onClick = { onClick = {
@@ -587,7 +587,7 @@ private fun ServerConfigSheetContent(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
enabled = name.isNotBlank() && url.isNotBlank() enabled = name.isNotBlank() && url.isNotBlank()
) { ) {
Text("保存") AppText("保存")
} }
} }
} }
@@ -625,9 +625,9 @@ private fun PathNavigationBar(
itemsIndexed(pathNames) { index, name -> itemsIndexed(pathNames) { index, name ->
val isLast = index == pathNames.lastIndex val isLast = index == pathNames.lastIndex
Text( AppText(
text = name, text = name,
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium, fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium,
color = if (isLast) color = if (isLast)
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
@@ -716,9 +716,9 @@ private fun RemoteBookItem(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
Text( AppText(
text = book.filename.substringBeforeLast("."), text = book.filename.substringBeforeLast("."),
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -732,7 +732,7 @@ private fun RemoteBookItem(
if (book.contentType.isNotEmpty()) { if (book.contentType.isNotEmpty()) {
TextCard( TextCard(
text = book.contentType.uppercase(), text = book.contentType.uppercase(),
textStyle = MaterialTheme.typography.labelSmall, textStyle = LegadoTheme.typography.labelSmall,
horizontalPadding = 4.dp, horizontalPadding = 4.dp,
verticalPadding = 2.dp, verticalPadding = 2.dp,
cornerRadius = 4.dp, cornerRadius = 4.dp,
@@ -743,11 +743,11 @@ private fun RemoteBookItem(
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
} }
Text( AppText(
text = "${ConvertUtils.formatFileSize(book.size)}${ text = "${ConvertUtils.formatFileSize(book.size)}${
AppConst.dateFormat.format(book.lastModify) AppConst.dateFormat.format(book.lastModify)
}", }",
style = MaterialTheme.typography.labelMedium, style = LegadoTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -30,7 +30,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -47,12 +46,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.book.changecover.ChangeCoverDialog import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.ui.widget.components.AppScaffold 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.TopBarButtonVariant
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.cover.Cover 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.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.SelectImageContract
import io.legado.app.utils.launch import io.legado.app.utils.launch
import io.legado.app.utils.showDialogFragment import io.legado.app.utils.showDialogFragment
@@ -71,7 +71,7 @@ fun BookInfoEditScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { Text(text = stringResource(id = R.string.book_info_edit)) }, title = stringResource(id = R.string.book_info_edit),
navigationIcon = { navigationIcon = {
TopbarNavigationButton( TopbarNavigationButton(
onClick = onBack, onClick = onBack,
@@ -191,35 +191,35 @@ fun BookInfoEditContent(
OutlinedTextField( OutlinedTextField(
value = uiState.name, value = uiState.name,
onValueChange = { viewModel.onNameChange(it) }, onValueChange = { viewModel.onNameChange(it) },
label = { Text("书名") }, label = { AppText("书名") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = uiState.author, value = uiState.author,
onValueChange = { viewModel.onAuthorChange(it) }, onValueChange = { viewModel.onAuthorChange(it) },
label = { Text("作者") }, label = { AppText("作者") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = uiState.coverUrl ?: "", value = uiState.coverUrl ?: "",
onValueChange = { viewModel.onCoverUrlChange(it) }, onValueChange = { viewModel.onCoverUrlChange(it) },
label = { Text("封面链接") }, label = { AppText("封面链接") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = uiState.intro ?: "", value = uiState.intro ?: "",
onValueChange = { viewModel.onIntroChange(it) }, onValueChange = { viewModel.onIntroChange(it) },
label = { Text("简介") }, label = { AppText("简介") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = uiState.remark ?: "", value = uiState.remark ?: "",
onValueChange = { viewModel.onRemarkChange(it) }, onValueChange = { viewModel.onRemarkChange(it) },
label = { Text("备注") }, label = { AppText("备注") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
@@ -251,7 +251,7 @@ fun BookTypeDropdown(
state = textFieldState, state = textFieldState,
readOnly = true, readOnly = true,
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
label = { Text("书籍类型") }, label = { AppText("书籍类型") },
trailingIcon = { trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
}, },
@@ -269,7 +269,7 @@ fun BookTypeDropdown(
) { ) {
bookTypes.forEach { option -> bookTypes.forEach { option ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(option) }, text = { AppText(option) },
onClick = { onClick = {
onTypeSelected(option) onTypeSelected(option)
expanded = false 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.Search
import androidx.compose.material.icons.filled.Timeline import androidx.compose.material.icons.filled.Timeline
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -78,15 +73,15 @@ import androidx.compose.ui.zIndex
import cn.hutool.core.date.DateUtil import cn.hutool.core.date.DateUtil
import io.legado.app.data.entities.readRecord.ReadRecord import io.legado.app.data.entities.readRecord.ReadRecord
import io.legado.app.data.entities.readRecord.ReadRecordDetail import io.legado.app.data.entities.readRecord.ReadRecordDetail
import io.legado.app.ui.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.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView 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.SearchBarSection
import io.legado.app.ui.widget.components.SectionHeader
import io.legado.app.ui.widget.components.button.AlertButton import io.legado.app.ui.widget.components.button.AlertButton
import io.legado.app.ui.widget.components.button.AppIconButton
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar
import io.legado.app.ui.widget.components.heatmap.HeatmapConfig import io.legado.app.ui.widget.components.heatmap.HeatmapConfig
@@ -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.rememberDateRange
import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange
import io.legado.app.ui.widget.components.heatmap.rememberWeeks import io.legado.app.ui.widget.components.heatmap.rememberWeeks
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.swipe.SwipeAction import io.legado.app.ui.widget.components.swipe.SwipeAction
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
import io.legado.app.ui.widget.components.text.AnimatedTextLine
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.utils.StringUtils.formatFriendlyDate import io.legado.app.utils.StringUtils.formatFriendlyDate
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -148,11 +148,7 @@ fun ReadRecordScreen(
topBar = { topBar = {
Column { Column {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = "阅读记录",
Text(
text = "阅读记录"
)
},
subtitle = { subtitle = {
val subTitle = when (displayMode) { val subTitle = when (displayMode) {
DisplayMode.AGGREGATE -> "汇总视图" DisplayMode.AGGREGATE -> "汇总视图"
@@ -167,7 +163,7 @@ fun ReadRecordScreen(
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
}, },
actions = { actions = {
IconButton(onClick = { AppIconButton(onClick = {
val newMode = when (displayMode) { val newMode = when (displayMode) {
DisplayMode.AGGREGATE -> DisplayMode.TIMELINE DisplayMode.AGGREGATE -> DisplayMode.TIMELINE
DisplayMode.TIMELINE -> DisplayMode.LATEST DisplayMode.TIMELINE -> DisplayMode.LATEST
@@ -181,13 +177,16 @@ fun ReadRecordScreen(
DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List
} }
val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate" val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate"
Icon(icon, description) AppIcon(icon, description)
} }
IconButton(onClick = { showCalendar = !showCalendar }) { AppIconButton(onClick = { showCalendar = !showCalendar }) {
Icon(Icons.Default.CalendarMonth, contentDescription = "Toggle Calendar") AppIcon(
Icons.Default.CalendarMonth,
contentDescription = "Toggle Calendar"
)
} }
IconButton(onClick = { showSearch = !showSearch }) { AppIconButton(onClick = { showSearch = !showSearch }) {
Icon(Icons.Default.Search, contentDescription = null) AppIcon(Icons.Default.Search, contentDescription = null)
} }
}, },
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
@@ -271,10 +270,10 @@ fun ReadRecordScreen(
onDismissRequest = { onDismissRequest = {
pendingDeleteAction = null pendingDeleteAction = null
}, },
title = { Text("确认删除") }, title = { AppText("确认删除") },
text = { text = {
Column { Column {
Text("确定要删除这条记录吗?") AppText("确定要删除这条记录吗?")
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Row( Row(
modifier = Modifier modifier = Modifier
@@ -289,10 +288,10 @@ fun ReadRecordScreen(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Checkbox(checked = skipDeleteConfirmTemp, onCheckedChange = null) Checkbox(checked = skipDeleteConfirmTemp, onCheckedChange = null)
Text( AppText(
text = "不再提示", text = "不再提示",
modifier = Modifier.padding(start = 8.dp), modifier = Modifier.padding(start = 8.dp),
style = MaterialTheme.typography.bodyMedium style = LegadoTheme.typography.bodyMedium
) )
} }
} }
@@ -311,30 +310,29 @@ fun ReadRecordScreen(
TextButton(onClick = { TextButton(onClick = {
pendingDeleteAction = null pendingDeleteAction = null
}) { }) {
Text("取消") AppText("取消")
} }
} }
) )
} }
if (showCalendar) { AppModalBottomSheet(
GlassModalBottomSheet( show = showCalendar,
onDismissRequest = { showCalendar = false } onDismissRequest = { showCalendar = false }
) { ) {
HeatmapCalendarSection( HeatmapCalendarSection(
dailyReadCounts = state.dailyReadCounts, dailyReadCounts = state.dailyReadCounts,
dailyReadTimes = state.dailyReadTimes, dailyReadTimes = state.dailyReadTimes,
selectedDate = state.selectedDate, selectedDate = state.selectedDate,
onDateSelected = { date -> onDateSelected = { date ->
viewModel.setSelectedDate(date) viewModel.setSelectedDate(date)
showCalendar = false showCalendar = false
}, },
onClearDate = { onClearDate = {
viewModel.setSelectedDate(null) viewModel.setSelectedDate(null)
showCalendar = false showCalendar = false
} }
) )
}
} }
@@ -346,10 +344,10 @@ fun ReadRecordScreen(
AlertDialog( AlertDialog(
onDismissRequest = { mergeDialogData = null }, onDismissRequest = { mergeDialogData = null },
title = { Text("合并阅读记录") }, title = { AppText("合并阅读记录") },
text = { text = {
Column { Column {
Text("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}") AppText("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
candidates.forEach { candidate -> candidates.forEach { candidate ->
@@ -367,10 +365,10 @@ fun ReadRecordScreen(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Checkbox(checked = isChecked, onCheckedChange = null) Checkbox(checked = isChecked, onCheckedChange = null)
Text( AppText(
text = "$author${formatDuring(candidate.readTime)}", text = "$author${formatDuring(candidate.readTime)}",
modifier = Modifier.padding(start = 8.dp), modifier = Modifier.padding(start = 8.dp),
style = MaterialTheme.typography.bodyMedium style = LegadoTheme.typography.bodyMedium
) )
} }
} }
@@ -386,12 +384,12 @@ fun ReadRecordScreen(
mergeDialogData = null mergeDialogData = null
} }
) { ) {
Text("合并") AppText("合并")
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { mergeDialogData = null }) { TextButton(onClick = { mergeDialogData = null }) {
Text("取消") AppText("取消")
} }
} }
) )
@@ -686,21 +684,21 @@ fun LatestReadItem(
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( AppText(
text = record.bookName, text = record.bookName,
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Text( AppText(
text = record.bookAuthor.ifBlank { "未知作者" }, text = record.bookAuthor.ifBlank { "未知作者" },
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline, color = MaterialTheme.colorScheme.outline,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
modifier = Modifier modifier = Modifier
.basicMarquee( .basicMarquee(
iterations = Int.MAX_VALUE, iterations = Int.MAX_VALUE,
@@ -716,7 +714,7 @@ fun LatestReadItem(
append("${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}") append("${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}")
} }
}, },
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -783,9 +781,9 @@ fun TimelineSessionItem(
modifier = Modifier.width(48.dp), modifier = Modifier.width(48.dp),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Text( AppText(
text = endTimeText, text = endTimeText,
style = MaterialTheme.typography.bodySmall style = LegadoTheme.typography.bodySmall
) )
} }
@@ -795,23 +793,23 @@ fun TimelineSessionItem(
Cover(coverPath) Cover(coverPath)
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Column { Column {
Text( AppText(
text = session.bookName, text = session.bookName,
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Text( AppText(
text = session.bookAuthor.ifBlank { "未知作者" }, text = session.bookAuthor.ifBlank { "未知作者" },
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline, color = MaterialTheme.colorScheme.outline,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
text = chapterTitle.orEmpty(), text = chapterTitle.orEmpty(),
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.outline, color = MaterialTheme.colorScheme.outline,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -847,21 +845,21 @@ fun ReadRecordItem(
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( AppText(
text = detail.bookName, text = detail.bookName,
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
maxLines = 1 maxLines = 1
) )
Text( AppText(
text = detail.bookAuthor.ifBlank { "未知作者" }, text = detail.bookAuthor.ifBlank { "未知作者" },
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline, color = MaterialTheme.colorScheme.outline,
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
text = "阅读时长: ${formatDuring(detail.readTime)}", text = "阅读时长: ${formatDuring(detail.readTime)}",
color = MaterialTheme.colorScheme.outline, color = MaterialTheme.colorScheme.outline,
style = MaterialTheme.typography.labelSmall style = LegadoTheme.typography.labelSmall
) )
} }
} }
@@ -872,28 +870,27 @@ fun DateHeader(
date: String, date: String,
dailyTotalTime: Long? = null dailyTotalTime: Long? = null
) { ) {
val dateText = formatFriendlyDate(date) CollapsibleHeader(
SectionHeader( showIcon = false,
isCollapsed = false,
onToggle = { },
title = "",
titleContent = { titleContent = {
Column { val dateText = formatFriendlyDate(date)
Text( AppText(
text = dateText, text = dateText,
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.secondary 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 val totalDurationMinutes = totalTimeMillis / 60000
Card( GlassCard(
onClick = onClick, onClick = onClick,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
colors = CardDefaults.cardColors( containerColor = LegadoTheme.colorScheme.surfaceContainer
containerColor = MaterialTheme.colorScheme.surfaceContainer
)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -934,28 +929,28 @@ fun ReadingSummaryCard(
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( AppText(
text = title, text = title,
style = MaterialTheme.typography.labelLarge, style = LegadoTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary color = LegadoTheme.colorScheme.primary
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Row(verticalAlignment = Alignment.Bottom) { Row(verticalAlignment = Alignment.Bottom) {
Text( AppText(
text = "已读 ", text = "已读 ",
style = MaterialTheme.typography.titleMedium style = LegadoTheme.typography.titleMedium
) )
Text( AppText(
text = "$bookCount", text = "$bookCount",
style = MaterialTheme.typography.headlineMedium, style = LegadoTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary, color = LegadoTheme.colorScheme.primary,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
Text( AppText(
text = " 本书", text = " 本书",
style = MaterialTheme.typography.titleMedium style = LegadoTheme.typography.titleMedium
) )
} }
@@ -965,10 +960,10 @@ fun ReadingSummaryCard(
val minutes = totalDurationMinutes % 60 val minutes = totalDurationMinutes % 60
val timeString = if (hours > 0) "${hours}小时${minutes}分钟" else "${minutes}分钟" val timeString = if (hours > 0) "${hours}小时${minutes}分钟" else "${minutes}分钟"
Text( AppText(
text = "共阅读 $timeString", text = "共阅读 $timeString",
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -44,7 +44,6 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TooltipDefaults
@@ -64,11 +63,9 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.SearchContentHistory 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.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView 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.SearchBarSection
import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton
import io.legado.app.ui.widget.components.button.SmallIconButton 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.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn 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 kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -140,12 +140,9 @@ fun SearchContentScreen(
topBar = { topBar = {
Column { Column {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
val title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) { "${searchResults.size} 条结果"
"${searchResults.size} 条结果" } else "搜索内容",
} else "搜索内容"
AnimatedText(text = title)
},
navigationIcon = { TopbarNavigationButton(onClick = onBack) }, navigationIcon = { TopbarNavigationButton(onClick = onBack) },
actions = { actions = {
Row( Row(
@@ -191,7 +188,7 @@ fun SearchContentScreen(
), ),
tooltip = { tooltip = {
PlainTooltip { PlainTooltip {
Text(if (isSearching) "停止搜索" else "跳转到当前章节") AppText(if (isSearching) "停止搜索" else "跳转到当前章节")
} }
}, },
state = rememberTooltipState(), state = rememberTooltipState(),
@@ -310,10 +307,10 @@ fun SearchHistoryList(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 8.dp)
) { ) {
Text( AppText(
text = "搜索历史", text = "搜索历史",
style = MaterialTheme.typography.titleSmallEmphasized, style = LegadoTheme.typography.titleSmallEmphasized,
color = MaterialTheme.colorScheme.primary, color = LegadoTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
SmallAnimatedActionButton( SmallAnimatedActionButton(
@@ -342,7 +339,7 @@ fun SearchHistoryList(
.clickable { onHistoryClick(item) } .clickable { onHistoryClick(item) }
.animateItem(), .animateItem(),
headlineContent = { headlineContent = {
Text( AppText(
text = item.query, text = item.query,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -378,7 +375,7 @@ fun SearchHistoryList(
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("清除搜索历史") AppText("清除搜索历史")
} }
} }
} }
@@ -407,7 +404,7 @@ fun SearchResultItem(
Box(modifier = Modifier.padding(16.dp)) { Box(modifier = Modifier.padding(16.dp)) {
Column { Column {
Text( AppText(
text = buildAnnotatedString { text = buildAnnotatedString {
append( append(
result.getTitleSpannable( result.getTitleSpannable(
@@ -415,14 +412,14 @@ fun SearchResultItem(
) )
) )
}, },
style = MaterialTheme.typography.titleSmall style = LegadoTheme.typography.titleSmall
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
HorizontalDivider() HorizontalDivider()
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
text = buildAnnotatedString { text = buildAnnotatedString {
append( append(
result.getContentSpannable( 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.PrimaryScrollableTabRow
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleFloatingActionButton import androidx.compose.material3.ToggleFloatingActionButton
import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon
import androidx.compose.material3.animateFloatingActionButton 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.help.book.isLocal
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
import io.legado.app.ui.replace.ReplaceEditRoute 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.CollapsibleHeader
import io.legado.app.ui.widget.components.ActionItem import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.SelectionBottomBar 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.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem 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.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar 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.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -312,33 +313,33 @@ fun TocScreen(
when (pagerState.currentPage) { when (pagerState.currentPage) {
0 -> { 0 -> {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("使用替换规则") }, text = "使用替换规则",
trailingIcon = { trailingIcon = {
Checkbox(checked = useReplace, onCheckedChange = null) Checkbox(checked = useReplace, onCheckedChange = null)
}, },
onClick = { viewModel.toggleUseReplace() } onClick = { viewModel.toggleUseReplace() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("显示字数") }, text = "显示字数",
trailingIcon = { trailingIcon = {
Checkbox(checked = showWordCount, onCheckedChange = null) Checkbox(checked = showWordCount, onCheckedChange = null)
}, },
onClick = { viewModel.toggleShowWordCount() } onClick = { viewModel.toggleShowWordCount() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("反转目录") }, text = "反转目录",
onClick = { viewModel.reverseToc() } onClick = { viewModel.reverseToc() }
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("替换规则") }, text = "替换规则",
onClick = { onClick = {
onOpenReplaceRule(null) onOpenReplaceRule(null)
dismiss() dismiss()
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("新建替换规则") }, text = "新建替换规则",
onClick = { onClick = {
val scopes = mutableListOf<String>() val scopes = mutableListOf<String>()
book?.name?.let { scopes.add(it) } book?.name?.let { scopes.add(it) }
@@ -358,7 +359,7 @@ fun TocScreen(
if (book?.isLocal == true) { if (book?.isLocal == true) {
PillHeaderDivider(title = "本地书籍选项") PillHeaderDivider(title = "本地书籍选项")
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("本地书籍目录规则") }, text = "本地书籍目录规则",
onClick = { onClick = {
val intent = val intent =
Intent(context, TxtTocRuleActivity::class.java).apply { Intent(context, TxtTocRuleActivity::class.java).apply {
@@ -369,7 +370,7 @@ fun TocScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("拆分超长章节") }, text = "拆分超长章节",
trailingIcon = { trailingIcon = {
Checkbox( Checkbox(
checked = viewModel.isSplitLongChapter, checked = viewModel.isSplitLongChapter,
@@ -386,7 +387,7 @@ fun TocScreen(
else -> { else -> {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("导出书签为JSON") }, text = "导出书签为JSON",
onClick = { onClick = {
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm", "yyyyMMdd_HHmm",
@@ -398,7 +399,7 @@ fun TocScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("导出书签为MarkDown") }, text = "导出书签为MarkDown",
onClick = { onClick = {
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm", "yyyyMMdd_HHmm",
@@ -428,7 +429,7 @@ fun TocScreen(
selected = pagerState.currentPage == 0, selected = pagerState.currentPage == 0,
onClick = { scope.launch { pagerState.animateScrollToPage(0) } }, onClick = { scope.launch { pagerState.animateScrollToPage(0) } },
text = { text = {
Text( AppText(
text = "目录", text = "目录",
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -439,7 +440,7 @@ fun TocScreen(
selected = pagerState.currentPage == 1, selected = pagerState.currentPage == 1,
onClick = { scope.launch { pagerState.animateScrollToPage(1) } }, onClick = { scope.launch { pagerState.animateScrollToPage(1) } },
text = { text = {
Text( AppText(
text = "书签", text = "书签",
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -464,13 +465,13 @@ fun TocScreen(
onDismissRequest = { showVolumeMenu = false } onDismissRequest = { showVolumeMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("展开所有卷") }, text = "展开所有卷",
onClick = { onClick = {
viewModel.expandAllVolumes(); showVolumeMenu = false viewModel.expandAllVolumes(); showVolumeMenu = false
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("收起所有卷") }, text = "收起所有卷",
onClick = { onClick = {
viewModel.collapseAllVolumes(); showVolumeMenu = false viewModel.collapseAllVolumes(); showVolumeMenu = false
} }
@@ -482,7 +483,7 @@ fun TocScreen(
PillHeaderDivider(title = "快速跳转") PillHeaderDivider(title = "快速跳转")
volumeItems.forEach { uiItem -> volumeItems.forEach { uiItem ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(uiItem.title) }, text = uiItem.title,
onClick = { onClick = {
scope.launch { scope.launch {
val targetIndex = val targetIndex =
@@ -541,7 +542,7 @@ fun TocScreen(
fabMenuExpanded = false fabMenuExpanded = false
}, },
icon = { Icon(icon, contentDescription = null) }, icon = { Icon(icon, contentDescription = null) },
text = { Text(text = label) } text = { AppText(text = label) }
) )
} }
} }
@@ -593,20 +594,19 @@ fun TocScreen(
} }
} }
if (editingBookmark != null) { BookmarkEditSheet(
BookmarkEditSheet( show = editingBookmark != null,
bookmark = editingBookmark!!, bookmark = editingBookmark!!,
onDismiss = { editingBookmark = null }, onDismiss = { editingBookmark = null },
onSave = { updatedBookmark -> onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark) viewModel.updateBookmark(updatedBookmark)
editingBookmark = null editingBookmark = null
}, },
onDelete = { bookmarkToDelete -> onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete) viewModel.deleteBookmark(bookmarkToDelete)
editingBookmark = null editingBookmark = null
} }
) )
}
} }
} }
@@ -729,9 +729,9 @@ fun ChapterItem(
) )
} }
Text( AppText(
text = item.title, text = item.title,
style = MaterialTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium), style = LegadoTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
color = textColor, color = textColor,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -739,9 +739,9 @@ fun ChapterItem(
} }
if (!item.tag.isNullOrEmpty()) { if (!item.tag.isNullOrEmpty()) {
Text( AppText(
text = item.tag, text = item.tag,
style = MaterialTheme.typography.labelSmallEmphasized, style = LegadoTheme.typography.labelSmallEmphasized,
color = detailColor.copy(alpha = 0.8f), color = detailColor.copy(alpha = 0.8f),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -910,11 +910,11 @@ private fun StatusIcon(
color = Color.Transparent color = Color.Transparent
) { ) {
if (wordCount != null) { if (wordCount != null) {
Text( AppText(
modifier = Modifier modifier = Modifier
.padding(horizontal = 8.dp, vertical = 4.dp), .padding(horizontal = 8.dp, vertical = 4.dp),
text = wordCount, text = wordCount,
style = MaterialTheme.typography.labelSmall.copy(fontSize = 9.sp), style = LegadoTheme.typography.labelSmall.copy(fontSize = 9.sp),
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable 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.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -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.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet 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.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -154,54 +151,49 @@ fun TxtRuleScreen(
) )
} }
if (showExportSheet) { FilePickerSheet(
FilePickerSheet( show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export), title = stringResource(R.string.export),
onSelectSysDir = { onSelectSysDir = {
showExportSheet = false showExportSheet = false
exportDoc.launch("exportDictRule.json") exportDoc.launch("exportDictRule.json")
}, },
onUpload = { onUpload = {
showExportSheet = false showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules) viewModel.uploadSelectedRules(selectedIds, rules)
}, },
allowExtensions = arrayOf("json") 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 -> FilePickerSheet(
BatchImportDialog( show = showImportSheet,
title = "导入词典规则", onDismissRequest = { showImportSheet = false },
importState = state, title = stringResource(R.string.import_txt_toc_rule),
onDismissRequest = { viewModel.cancelImport() }, onSelectSysFile = { types ->
onToggleItem = { viewModel.toggleImportSelection(it) }, importDoc.launch(types)
onToggleAll = { viewModel.toggleImportAll(it) }, showImportSheet = false
onConfirm = { viewModel.saveImportedRules() }, },
itemContent = { rule, _ -> onManualInput = {
Column { showUrlInput = true
Text(rule.name, style = MaterialTheme.typography.titleMedium) showImportSheet = false
Text(rule.rule, style = MaterialTheme.typography.bodySmall, maxLines = 1) },
} 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) { LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) {
@@ -212,16 +204,16 @@ fun TxtRuleScreen(
showDeleteRuleDialog?.let { rule -> showDeleteRuleDialog?.let { rule ->
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null }, onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) }, title = { AppText(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) }, text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
viewModel.delete(rule); showDeleteRuleDialog = null viewModel.delete(rule); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) } }) { AppText(stringResource(R.string.ok)) }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) { TextButton(onClick = { showDeleteRuleDialog = null }) {
Text( AppText(
stringResource(R.string.cancel) stringResource(R.string.cancel)
) )
} }
@@ -229,48 +221,47 @@ fun TxtRuleScreen(
) )
} }
if (showEditSheet) { RuleEditSheet(
RuleEditSheet( show = showEditSheet,
rule = editingRule, rule = editingRule,
title = stringResource(R.string.txt_toc_rule), title = stringResource(R.string.txt_toc_rule),
label1 = stringResource(R.string.regex), label1 = stringResource(R.string.regex),
label2 = stringResource(R.string.example), label2 = stringResource(R.string.example),
onDismissRequest = { onDismissRequest = {
showEditSheet = false showEditSheet = false
editingRule = null editingRule = null
}, },
onSave = { updatedRule -> onSave = { updatedRule ->
//TODO:我很想把他改为自增主键,但为了兼容性日后再说 //TODO:我很想把他改为自增主键,但为了兼容性日后再说
if (editingRule == null) { if (editingRule == null) {
viewModel.insert(updatedRule) viewModel.insert(updatedRule)
} else { } else {
viewModel.update(updatedRule) 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
)
} }
) 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( RuleListScaffold(
title = if (isPickMode) "选择目录规则" else "目录规则", title = if (isPickMode) "选择目录规则" else "目录规则",
@@ -312,7 +303,7 @@ fun TxtRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_str)) }, text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -14,11 +13,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.AppScaffold 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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -36,7 +35,7 @@ fun ConfigNavScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.setting)) }, title = stringResource(R.string.setting),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
@@ -19,7 +19,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable 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.Permissions
import io.legado.app.lib.permission.PermissionsCompat import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.ui.widget.components.AppScaffold 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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet 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.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.InputSettingItem import io.legado.app.ui.widget.components.settingItem.InputSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem 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.isContentScheme
import io.legado.app.utils.takePersistablePermissionSafely import io.legado.app.utils.takePersistablePermissionSafely
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -166,9 +166,7 @@ fun BackupConfigScreen(
}, },
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = stringResource(R.string.backup_restore),
Text(stringResource(R.string.backup_restore))
},
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
@@ -387,19 +385,19 @@ fun BackupConfigScreen(
if (showWebDavAuthDialog) { if (showWebDavAuthDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showWebDavAuthDialog = false }, onDismissRequest = { showWebDavAuthDialog = false },
title = { Text(stringResource(R.string.web_dav_account)) }, title = { AppText(stringResource(R.string.web_dav_account)) },
text = { text = {
Column { Column {
TextField( TextField(
value = tempAccount, value = tempAccount,
onValueChange = { tempAccount = it }, onValueChange = { tempAccount = it },
label = { Text("账号") } label = { AppText("账号") }
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
TextField( TextField(
value = tempPassword, value = tempPassword,
onValueChange = { tempPassword = it }, onValueChange = { tempPassword = it },
label = { Text("密码") }, label = { AppText("密码") },
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = { trailingIcon = {
@@ -432,12 +430,12 @@ fun BackupConfigScreen(
} }
} }
}) { }) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showWebDavAuthDialog = false }) { TextButton(onClick = { showWebDavAuthDialog = false }) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
} }
) )
@@ -455,36 +453,35 @@ fun BackupConfigScreen(
) )
} }
if (showBackupFilePicker) { FilePickerSheet(
FilePickerSheet( show = showBackupFilePicker,
onDismissRequest = { showBackupFilePicker = false }, onDismissRequest = { showBackupFilePicker = false },
onSelectSysDir = { onSelectSysDir = {
showBackupFilePicker = false showBackupFilePicker = false
try { try {
selectBackupPathLauncher.launch(null) selectBackupPathLauncher.launch(null)
} catch (e: Exception) { } catch (e: Exception) {
}
} }
) }
} )
if (showRestoreFilePicker) {
FilePickerSheet( FilePickerSheet(
onDismissRequest = { showRestoreFilePicker = false }, show = showRestoreFilePicker,
onSelectSysDir = { onDismissRequest = { showRestoreFilePicker = false },
showRestoreFilePicker = false onSelectSysDir = {
try { showRestoreFilePicker = false
backupAndSelectLauncher.launch(null) try {
} catch (e: Exception) { backupAndSelectLauncher.launch(null)
} } catch (e: Exception) {
} }
) }
} )
if (showRestoreSheet && backupNames.isNotEmpty()) { if (showRestoreSheet && backupNames.isNotEmpty()) {
AlertDialog( AlertDialog(
onDismissRequest = { showRestoreSheet = false }, onDismissRequest = { showRestoreSheet = false },
title = { Text(stringResource(R.string.select_restore_file)) }, title = { AppText(stringResource(R.string.select_restore_file)) },
text = { text = {
Column { Column {
backupNames.forEach { name -> backupNames.forEach { name ->
@@ -508,7 +505,7 @@ fun BackupConfigScreen(
} }
) )
}) { }) {
Text(name) AppText(name)
} }
} }
} }
@@ -516,7 +513,7 @@ fun BackupConfigScreen(
confirmButton = {}, confirmButton = {},
dismissButton = { dismissButton = {
TextButton(onClick = { showRestoreSheet = false }) { 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() io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
showBackupIgnoreDialog = false showBackupIgnoreDialog = false
}, },
title = { Text(stringResource(R.string.restore_ignore)) }, title = { AppText(stringResource(R.string.restore_ignore)) },
text = { text = {
Column { Column {
io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title -> io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title ->
@@ -546,7 +543,7 @@ fun BackupConfigScreen(
] = checkedItems[index] ] = checkedItems[index]
}) { }) {
val isChecked = 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() io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
showBackupIgnoreDialog = false showBackupIgnoreDialog = false
}) { }) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
@@ -564,7 +561,7 @@ fun BackupConfigScreen(
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig() io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
showBackupIgnoreDialog = false showBackupIgnoreDialog = false
}) { }) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
} }
) )
@@ -573,7 +570,7 @@ fun BackupConfigScreen(
if (showLoadingDialog) { if (showLoadingDialog) {
AlertDialog( AlertDialog(
onDismissRequest = {}, onDismissRequest = {},
title = { Text(loadingText) }, title = { AppText(loadingText) },
confirmButton = {}, confirmButton = {},
dismissButton = {} dismissButton = {}
) )
@@ -589,16 +586,16 @@ fun ConfirmDialog(
) { ) {
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
title = { Text(title) }, title = { AppText(title) },
text = { Text(text) }, text = { AppText(text) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = onConfirm) { OutlinedButton(onClick = onConfirm) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = onDismiss) { 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.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -27,14 +26,14 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.ui.widget.components.AppScaffold 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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet 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.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem 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.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 import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -52,7 +51,7 @@ fun CoverConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.cover_config)) }, title = stringResource(R.string.cover_config),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
@@ -247,17 +246,17 @@ fun CoverConfigScreen(
} }
} }
if (showCoverRuleSheet) { CoverRuleConfigSheet(
CoverRuleConfigSheet(onDismissRequest = { showCoverRuleSheet = false }) show = showCoverRuleSheet,
} onDismissRequest = { showCoverRuleSheet = false }
)
manageKey?.let { key -> CoverManageSheet(
CoverManageSheet( show = manageKey != null,
preferenceKey = key, preferenceKey = manageKey!!,
onDismissRequest = { manageKey = null }, onDismissRequest = { manageKey = null },
viewModel = viewModel viewModel = viewModel
) )
}
showColorPickerByField?.let { field -> showColorPickerByField?.let { field ->
val initialColor = when (field) { val initialColor = when (field) {
@@ -269,6 +268,7 @@ fun CoverConfigScreen(
} }
ColorPickerSheet( ColorPickerSheet(
show = showColorPickerByField != null,
initialColor = initialColor, initialColor = initialColor,
onDismissRequest = { showColorPickerByField = null }, onDismissRequest = { showColorPickerByField = null },
onColorSelected = { color -> onColorSelected = { color ->
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -36,12 +35,15 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CoverManageSheet( fun CoverManageSheet(
show: Boolean,
preferenceKey: String, preferenceKey: String,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: CoverConfigViewModel viewModel: CoverConfigViewModel
@@ -61,7 +63,8 @@ fun CoverManageSheet(
} }
val coverList = currentCovers.split(",").filter { it.isNotBlank() } val coverList = currentCovers.split(",").filter { it.isNotBlank() }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -69,9 +72,9 @@ fun CoverManageSheet(
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 24.dp) .padding(bottom = 24.dp)
) { ) {
Text( AppText(
text = stringResource(R.string.default_cover), text = stringResource(R.string.default_cover),
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) )
@@ -141,14 +144,13 @@ fun CoverManageSheet(
} }
} }
if (showFilePicker) { FilePickerSheet(
FilePickerSheet( show = showFilePicker,
onDismissRequest = { showFilePicker = false }, onDismissRequest = { showFilePicker = false },
onSelectSysFiles = { onSelectSysFiles = {
selectImages.launch("image/*") selectImages.launch("image/*")
showFilePicker = false showFilePicker = false
}, },
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp") allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
) )
}
} }
@@ -13,7 +13,6 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -27,8 +26,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.model.BookCover 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.settingItem.InputSettingItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -37,6 +38,7 @@ import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CoverRuleConfigSheet( fun CoverRuleConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
var enable by remember { mutableStateOf(false) } var enable by remember { mutableStateOf(false) }
@@ -52,7 +54,8 @@ fun CoverRuleConfigSheet(
coverRule = rule.coverRule coverRule = rule.coverRule
} }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -61,9 +64,9 @@ fun CoverRuleConfigSheet(
.padding(16.dp) .padding(16.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
Text( AppText(
text = stringResource(R.string.cover_rule), text = stringResource(R.string.cover_rule),
style = MaterialTheme.typography.titleLarge, style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp) modifier = Modifier.padding(bottom = 16.dp)
) )
@@ -75,7 +78,7 @@ fun CoverRuleConfigSheet(
checked = enable, checked = enable,
onCheckedChange = { enable = it } onCheckedChange = { enable = it }
) )
Text(text = stringResource(R.string.enable)) AppText(text = stringResource(R.string.enable))
} }
InputSettingItem( InputSettingItem(
@@ -100,13 +103,16 @@ fun CoverRuleConfigSheet(
BookCover.delCoverRule() BookCover.delCoverRule()
onDismissRequest() onDismissRequest()
}) { }) {
Text(stringResource(R.string.delete), color = MaterialTheme.colorScheme.error) AppText(
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
OutlinedButton(onClick = onDismissRequest) { OutlinedButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
Spacer(modifier = Modifier.padding(horizontal = 4.dp)) Spacer(modifier = Modifier.padding(horizontal = 4.dp))
@@ -121,7 +127,7 @@ fun CoverRuleConfigSheet(
onDismissRequest() 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.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -18,21 +17,25 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CheckSourceBottomSheet( fun CheckSourceBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel = koinViewModel(), viewModel: OtherConfigViewModel = koinViewModel(),
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss onDismissRequest = onDismiss
) { ) {
Column( Column(
@@ -41,9 +44,9 @@ fun CheckSourceBottomSheet(
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.padding(bottom = 32.dp) .padding(bottom = 32.dp)
) { ) {
Text( AppText(
text = stringResource(R.string.check_source_config), text = stringResource(R.string.check_source_config),
style = MaterialTheme.typography.titleLarge, style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp) modifier = Modifier.padding(vertical = 16.dp)
) )
@@ -131,7 +134,7 @@ fun CheckSourceBottomSheet(
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
TextButton(onClick = onDismiss) { TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
Button(onClick = { Button(onClick = {
if (viewModel.saveCheckSourceConfig()) { if (viewModel.saveCheckSourceConfig()) {
@@ -140,7 +143,7 @@ fun CheckSourceBottomSheet(
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show() 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.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -41,8 +40,10 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.DirectLinkUpload import io.legado.app.help.DirectLinkUpload
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getClipText import io.legado.app.utils.getClipText
@@ -52,6 +53,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DirectLinkUploadBottomSheet( fun DirectLinkUploadBottomSheet(
show: Boolean,
viewModel: OtherConfigViewModel, viewModel: OtherConfigViewModel,
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
@@ -63,7 +65,8 @@ fun DirectLinkUploadBottomSheet(
viewModel.initDirectLinkRule() viewModel.initDirectLinkRule()
} }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismiss onDismissRequest = onDismiss
) { ) {
Column( Column(
@@ -75,9 +78,9 @@ fun DirectLinkUploadBottomSheet(
) { ) {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = { title = {
Text( AppText(
text = stringResource(R.string.direct_link_upload_config), text = stringResource(R.string.direct_link_upload_config),
style = MaterialTheme.typography.titleLarge style = LegadoTheme.typography.titleLarge
) )
}, },
actions = { actions = {
@@ -89,7 +92,7 @@ fun DirectLinkUploadBottomSheet(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text("导入默认") }, text = { AppText("导入默认") },
leadingIcon = { Icon(Icons.Default.Download, null) }, leadingIcon = { Icon(Icons.Default.Download, null) },
onClick = { onClick = {
showMenu = false showMenu = false
@@ -99,7 +102,7 @@ fun DirectLinkUploadBottomSheet(
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.copy_rule)) }, text = { AppText(stringResource(R.string.copy_rule)) },
leadingIcon = { Icon(Icons.Default.ContentCopy, null) }, leadingIcon = { Icon(Icons.Default.ContentCopy, null) },
onClick = { onClick = {
showMenu = false showMenu = false
@@ -113,7 +116,7 @@ fun DirectLinkUploadBottomSheet(
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.paste_rule)) }, text = { AppText(stringResource(R.string.paste_rule)) },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) }, leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = { onClick = {
showMenu = false showMenu = false
@@ -141,7 +144,7 @@ fun DirectLinkUploadBottomSheet(
OutlinedTextField( OutlinedTextField(
value = viewModel.uploadUrl, value = viewModel.uploadUrl,
onValueChange = { viewModel.uploadUrl = it }, onValueChange = { viewModel.uploadUrl = it },
label = { Text(stringResource(R.string.upload_url)) }, label = { AppText(stringResource(R.string.upload_url)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
@@ -150,7 +153,7 @@ fun DirectLinkUploadBottomSheet(
OutlinedTextField( OutlinedTextField(
value = viewModel.downloadUrlRule, value = viewModel.downloadUrlRule,
onValueChange = { viewModel.downloadUrlRule = it }, onValueChange = { viewModel.downloadUrlRule = it },
label = { Text(stringResource(R.string.download_url_rule)) }, label = { AppText(stringResource(R.string.download_url_rule)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
@@ -159,7 +162,7 @@ fun DirectLinkUploadBottomSheet(
OutlinedTextField( OutlinedTextField(
value = viewModel.summary, value = viewModel.summary,
onValueChange = { viewModel.summary = it }, onValueChange = { viewModel.summary = it },
label = { Text(stringResource(R.string.summary)) }, label = { AppText(stringResource(R.string.summary)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
@@ -178,11 +181,11 @@ fun DirectLinkUploadBottomSheet(
TextButton(onClick = { TextButton(onClick = {
viewModel.testRule { result -> showTestResult = result } viewModel.testRule { result -> showTestResult = result }
}) { }) {
Text("测试") AppText("测试")
} }
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
OutlinedButton(onClick = onDismiss) { OutlinedButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
Button(onClick = { Button(onClick = {
if (viewModel.saveDirectLinkRule()) { if (viewModel.saveDirectLinkRule()) {
@@ -191,7 +194,7 @@ fun DirectLinkUploadBottomSheet(
context.toastOnUi("请填写完整信息") context.toastOnUi("请填写完整信息")
} }
}) { }) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
} }
} }
@@ -200,15 +203,15 @@ fun DirectLinkUploadBottomSheet(
showTestResult?.let { result -> showTestResult?.let { result ->
AlertDialog( AlertDialog(
onDismissRequest = { showTestResult = null }, onDismissRequest = { showTestResult = null },
title = { Text("Result") }, title = { AppText("Result") },
text = { Text(result) }, text = { AppText(result) },
confirmButton = { confirmButton = {
TextButton(onClick = { TextButton(onClick = {
showTestResult = null showTestResult = null
}) { Text(stringResource(R.string.ok)) } }) { AppText(stringResource(R.string.ok)) }
}, },
dismissButton = { 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.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -30,8 +29,6 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.service.WebService import io.legado.app.service.WebService
import io.legado.app.ui.widget.components.AppScaffold 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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet 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.InputSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem 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.restart
import io.legado.app.utils.takePersistablePermissionSafely import io.legado.app.utils.takePersistablePermissionSafely
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -83,9 +83,7 @@ fun OtherConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = stringResource(R.string.other_setting),
Text(stringResource(R.string.other_setting))
},
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
@@ -200,7 +198,10 @@ fun OtherConfigScreen(
SliderSettingItem( SliderSettingItem(
title = stringResource(R.string.bitmap_cache_size), 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(), value = OtherConfig.bitmapCacheSize.toFloat(),
defaultValue = 32f, defaultValue = 32f,
valueRange = 1f..2047f, valueRange = 1f..2047f,
@@ -211,7 +212,10 @@ fun OtherConfigScreen(
SliderSettingItem( SliderSettingItem(
title = stringResource(R.string.image_retain_number), 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(), value = OtherConfig.imageRetainNum.toFloat(),
defaultValue = 10f, defaultValue = 10f,
valueRange = 0f..100f, valueRange = 0f..100f,
@@ -220,7 +224,10 @@ fun OtherConfigScreen(
SliderSettingItem( SliderSettingItem(
title = stringResource(R.string.pre_download), 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(), value = OtherConfig.preDownloadNum.toFloat(),
defaultValue = 10f, defaultValue = 10f,
valueRange = 0f..100f, valueRange = 0f..100f,
@@ -391,33 +398,30 @@ fun OtherConfigScreen(
} }
} }
if (showFilePicker) { FilePickerSheet(
FilePickerSheet( show = showFilePicker,
onDismissRequest = { showFilePicker = false }, onDismissRequest = { showFilePicker = false },
onSelectSysDir = { onSelectSysDir = {
showFilePicker = false showFilePicker = false
try { try {
selectDocTree.launch(null) selectDocTree.launch(null)
} catch (e: Exception) { } catch (e: Exception) {
}
} }
) }
} )
if (showCheckSourceSheet) { CheckSourceBottomSheet(
CheckSourceBottomSheet( show = showCheckSourceSheet,
viewModel = viewModel, viewModel = viewModel,
onDismiss = { showCheckSourceSheet = false } onDismiss = { showCheckSourceSheet = false }
) )
}
if (showDirectLinkUploadSheet) { DirectLinkUploadBottomSheet(
DirectLinkUploadBottomSheet( show = showDirectLinkUploadSheet,
viewModel = viewModel, viewModel = viewModel,
onDismiss = { showDirectLinkUploadSheet = false } onDismiss = { showDirectLinkUploadSheet = false }
) )
}
if (showClearCacheDialog) { if (showClearCacheDialog) {
ConfirmDialog( ConfirmDialog(
@@ -458,23 +462,23 @@ fun OtherConfigScreen(
if (showPasswordDialog) { if (showPasswordDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showPasswordDialog = false }, onDismissRequest = { showPasswordDialog = false },
title = { Text(stringResource(R.string.set_local_password)) }, title = { AppText(stringResource(R.string.set_local_password)) },
text = { text = {
TextField( TextField(
value = tempPassword, value = tempPassword,
onValueChange = { tempPassword = it }, onValueChange = { tempPassword = it },
label = { Text("Password") } label = { AppText("Password") }
) )
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { TextButton(onClick = {
viewModel.setLocalPassword(tempPassword) viewModel.setLocalPassword(tempPassword)
showPasswordDialog = false showPasswordDialog = false
}) { Text(stringResource(R.string.ok)) } }) { AppText(stringResource(R.string.ok)) }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showPasswordDialog = false }) { TextButton(onClick = { showPasswordDialog = false }) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
} }
) )
@@ -492,16 +496,16 @@ fun ConfirmDialog(
) { ) {
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
title = { Text(title) }, title = { AppText(title) },
text = { Text(text) }, text = { AppText(text) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = onConfirm) { OutlinedButton(onClick = onConfirm) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = onDismiss) { 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.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -23,33 +22,36 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PageKeySheet( fun PageKeySheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) } var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) }
var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) } var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) }
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column( Column(
modifier = Modifier.Companion modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( AppText(
text = stringResource(R.string.custom_page_key), text = stringResource(R.string.custom_page_key),
style = MaterialTheme.typography.titleLarge, style = LegadoTheme.typography.titleLarge,
fontWeight = FontWeight.Companion.Bold fontWeight = FontWeight.Companion.Bold
) )
OutlinedTextField( OutlinedTextField(
value = prevKeys, value = prevKeys,
onValueChange = { prevKeys = it }, onValueChange = { prevKeys = it },
label = { Text(stringResource(R.string.prev_page_key)) }, label = { AppText(stringResource(R.string.prev_page_key)) },
modifier = Modifier.Companion modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
.onPreviewKeyEvent { event -> .onPreviewKeyEvent { event ->
@@ -72,7 +74,7 @@ fun PageKeySheet(
OutlinedTextField( OutlinedTextField(
value = nextKeys, value = nextKeys,
onValueChange = { nextKeys = it }, onValueChange = { nextKeys = it },
label = { Text(stringResource(R.string.next_page_key)) }, label = { AppText(stringResource(R.string.next_page_key)) },
modifier = Modifier.Companion modifier = Modifier.Companion
.fillMaxWidth() .fillMaxWidth()
.onPreviewKeyEvent { event -> .onPreviewKeyEvent { event ->
@@ -92,9 +94,9 @@ fun PageKeySheet(
singleLine = true singleLine = true
) )
Text( AppText(
text = stringResource(R.string.page_key_set_help), text = stringResource(R.string.page_key_set_help),
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -109,7 +111,7 @@ fun PageKeySheet(
}, },
modifier = Modifier.Companion.weight(1f) modifier = Modifier.Companion.weight(1f)
) { ) {
Text(stringResource(R.string.reset)) AppText(stringResource(R.string.reset))
} }
Button( Button(
@@ -120,7 +122,7 @@ fun PageKeySheet(
}, },
modifier = Modifier.Companion.weight(1f) 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.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -20,14 +19,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.AppScaffold 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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem 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.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem 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 io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -45,9 +44,7 @@ fun ReadConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = stringResource(R.string.read_config),
Text(stringResource(R.string.read_config))
},
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
@@ -323,7 +320,8 @@ fun ReadConfigScreen(
} }
} }
if (showPageKeySheet) { PageKeySheet(
PageKeySheet(onDismissRequest = { showPageKeySheet = false }) show = showPageKeySheet,
} onDismissRequest = { showPageKeySheet = false }
)
} }
@@ -17,7 +17,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -31,14 +30,17 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BackgroundImageManageSheet( fun BackgroundImageManageSheet(
show: Boolean,
isDarkTheme: Boolean, isDarkTheme: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: ThemeConfigViewModel = koinViewModel() viewModel: ThemeConfigViewModel = koinViewModel()
@@ -64,7 +66,8 @@ fun BackgroundImageManageSheet(
ThemeConfig.bgImageLight ThemeConfig.bgImageLight
} }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -72,9 +75,9 @@ fun BackgroundImageManageSheet(
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 24.dp) .padding(bottom = 24.dp)
) { ) {
Text( AppText(
text = stringResource(R.string.background_image), text = stringResource(R.string.background_image),
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) )
@@ -139,14 +142,13 @@ fun BackgroundImageManageSheet(
} }
} }
if (showFilePicker) { FilePickerSheet(
FilePickerSheet( show = showFilePicker,
onDismissRequest = { showFilePicker = false }, onDismissRequest = { showFilePicker = false },
onSelectSysFile = { onSelectSysFile = {
selectImage.launch("image/*") selectImage.launch("image/*")
showFilePicker = false showFilePicker = false
}, },
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp") 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.foundation.lazy.grid.items
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment 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.Launcher5
import io.legado.app.ui.main.Launcher6 import io.legado.app.ui.main.Launcher6
import io.legado.app.ui.main.LauncherW import io.legado.app.ui.main.LauncherW
import io.legado.app.ui.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 io.legado.app.utils.getCompatDrawable
import splitties.init.appCtx import splitties.init.appCtx
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LauncherIconPickerSheet( fun LauncherIconPickerSheet(
show: Boolean,
selectedValue: String, selectedValue: String,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onValueChange: (String) -> Unit onValueChange: (String) -> Unit
@@ -53,7 +55,8 @@ fun LauncherIconPickerSheet(
val context = LocalContext.current val context = LocalContext.current
val icons = LauncherIcons.list val icons = LauncherIcons.list
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -61,9 +64,9 @@ fun LauncherIconPickerSheet(
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 24.dp) .padding(bottom = 24.dp)
) { ) {
Text( AppText(
text = stringResource(R.string.change_icon), text = stringResource(R.string.change_icon),
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) )
@@ -15,12 +15,15 @@ object ThemeConfig {
var enableBlur by prefDelegate(PreferKey.enableBlur, false) var enableBlur by prefDelegate(PreferKey.enableBlur, false)
var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, true) var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, false)
var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true) var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true)
var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot") var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot")
//m3 or miuix
var composeEngine by prefDelegate(PreferKey.composeEngine, "material")
var materialVersion by prefDelegate(PreferKey.materialVersion, "material3") var materialVersion by prefDelegate(PreferKey.materialVersion, "material3")
var appTheme by prefDelegate(PreferKey.appTheme, "0") 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -78,11 +79,10 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.OldThemeConfig import io.legado.app.help.config.OldThemeConfig
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeManager import io.legado.app.ui.theme.ThemeManager
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.AppScaffold 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.SplicedColumnGroup
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet 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.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem 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.postEvent
import io.legado.app.utils.restart import io.legado.app.utils.restart
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
@@ -121,7 +128,7 @@ fun ThemeConfigScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { Text(stringResource(R.string.theme_setting)) }, title = stringResource(R.string.theme_setting),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBackClick) TopbarNavigationButton(onClick = onBackClick)
@@ -136,23 +143,29 @@ fun ThemeConfigScreen(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(16.dp) .padding(16.dp)
) { ) {
val composeEngine = ThemeConfig.composeEngine
val isMiuixEngine = remember(composeEngine) {
ThemeResolver.isMiuixEngine(composeEngine)
}
val isDarkTheme = when (selectedThemeMode) { val isDarkTheme = when (selectedThemeMode) {
"1" -> false "1" -> false
"2" -> true "2" -> true
else -> isSystemInDarkTheme() else -> isSystemInDarkTheme()
} }
Column( if (!isMiuixEngine) {
horizontalAlignment = Alignment.CenterHorizontally, Column(
modifier = Modifier.fillMaxWidth() horizontalAlignment = Alignment.CenterHorizontally,
) { modifier = Modifier.fillMaxWidth()
ThemeCard( ) {
context = context, ThemeCard(
value = selectedTheme, context = context,
isDark = isDarkTheme, value = selectedTheme,
isAmoled = ThemeConfig.isPureBlack, isDark = isDarkTheme,
paletteStyle = ThemeConfig.paletteStyle isAmoled = ThemeConfig.isPureBlack,
) paletteStyle = ThemeConfig.paletteStyle
)
}
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -163,47 +176,77 @@ fun ThemeConfigScreen(
themeItems.zip(themeValues).toList() themeItems.zip(themeValues).toList()
} }
if (isMiuixEngine) {
MiuixCard(
cornerRadius = 16.dp,
insideMargin = PaddingValues(16.dp),
colors = MiuixCardDefaults.defaultColors(
color = MiuixTheme.colorScheme.primaryVariant,
contentColor = MiuixTheme.colorScheme.onPrimary
)
) {
MiuixText("Miuix 目前为测试主题,且不对基于View的界面生效!")
}
}
SplicedColumnGroup(title = stringResource(R.string.theme)) { SplicedColumnGroup(title = stringResource(R.string.theme)) {
ThemeModeSelector( if (isMiuixEngine) {
selectedMode = selectedThemeMode, DropdownListSettingItem(
onModeSelected = { mode -> title = stringResource(R.string.theme_mode),
selectedThemeMode = mode selectedValue = selectedThemeMode,
ThemeConfig.themeMode = mode displayEntries = stringArrayResource(R.array.theme_mode),
OldThemeConfig.applyDayNight(context) 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( ThemeColorSelector(
context = context, context = context,
themes = themes, themes = themes,
selectedTheme = selectedTheme, selectedTheme = selectedTheme,
isDark = isDarkTheme, isDark = isDarkTheme,
isAmoled = ThemeConfig.isPureBlack, isAmoled = ThemeConfig.isPureBlack,
paletteStyle = ThemeConfig.paletteStyle, paletteStyle = ThemeConfig.paletteStyle,
onThemeSelected = { theme -> onThemeSelected = { theme ->
if (theme == "13") { if (theme == "13") {
val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty() val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty() val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
if (!hasLightBg || !hasDarkBg) { if (!hasLightBg || !hasDarkBg) {
context.toastOnUi(R.string.transparent_theme_alarm) context.toastOnUi(R.string.transparent_theme_alarm)
return@ThemeColorSelector 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 { } 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 { SplicedColumnGroup {
@@ -226,13 +269,22 @@ fun ThemeConfigScreen(
context.toastOnUi(R.string.restart_to_apply) 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( SliderSettingItem(
title = stringResource(R.string.font_scale), title = stringResource(R.string.font_scale),
description = stringResource( description = stringResource(
R.string.font_scale_summary, R.string.font_scale_summary,
AppContextWrapper.getFontScale(context) AppContextWrapper.getFontScale(context)
), ),
value = fontScaleValue.value, value = fontScaleValue.floatValue,
defaultValue = 10f, defaultValue = 10f,
valueRange = 8f..16f, valueRange = 8f..16f,
steps = 7, steps = 7,
@@ -478,7 +530,7 @@ fun ThemeConfigScreen(
if (showRestartDialog) { if (showRestartDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showRestartDialog = false }, onDismissRequest = { showRestartDialog = false },
title = { Text(stringResource(R.string.restart_required_message)) }, title = { AppText(stringResource(R.string.restart_required_message)) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
showRestartDialog = false showRestartDialog = false
@@ -486,7 +538,7 @@ fun ThemeConfigScreen(
context.restart() context.restart()
}, 100) }, 100)
}) { }) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
@@ -494,7 +546,7 @@ fun ThemeConfigScreen(
showRestartDialog = false showRestartDialog = false
context.toastOnUi(R.string.restart_later_message) 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 -> manageKey?.let { isDark ->
BackgroundImageManageSheet( BackgroundImageManageSheet(
show = true,
isDarkTheme = isDark, isDarkTheme = isDark,
onDismissRequest = { manageKey = null } 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) { ColorPickerSheet(
LauncherIconPickerSheet( show = showColorPicker,
selectedValue = ThemeConfig.launcherIcon, initialColor = primaryColorValue.value,
onDismissRequest = { showLauncherIconPicker = false }, onDismissRequest = { showColorPicker = false },
onValueChange = { onColorSelected = { color ->
ThemeConfig.launcherIcon = it primaryColorValue.value = color
LauncherIconHelp.changeIcon(it) 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) { LauncherIconPickerSheet(
ThemeListDialog( show = showLauncherIconPicker,
onDismissRequest = { showThemeListDialog = false } selectedValue = ThemeConfig.launcherIcon,
) onDismissRequest = { showLauncherIconPicker = false },
} onValueChange = {
ThemeConfig.launcherIcon = it
LauncherIconHelp.changeIcon(it)
}
)
ThemeListDialog(
show = showThemeListDialog,
onDismissRequest = { showThemeListDialog = false }
)
saveThemeKey?.let { key -> saveThemeKey?.let { key ->
AlertDialog( AlertDialog(
onDismissRequest = { saveThemeKey = null }, onDismissRequest = { saveThemeKey = null },
title = { Text(stringResource(R.string.theme_name)) }, title = { AppText(stringResource(R.string.theme_name)) },
text = { text = {
OutlinedTextField( OutlinedTextField(
value = themeName, value = themeName,
onValueChange = { themeName = it }, onValueChange = { themeName = it },
label = { Text("name") }, label = { AppText("name") },
singleLine = true singleLine = true
) )
}, },
@@ -566,12 +617,12 @@ fun ThemeConfigScreen(
saveThemeKey = null saveThemeKey = null
} }
) { ) {
Text(stringResource(android.R.string.ok)) AppText(stringResource(android.R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { saveThemeKey = null }) { 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)) Spacer(modifier = Modifier.height(12.dp))
Text( AppText(
text = label, text = label,
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface 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.FileDownload
import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -38,8 +36,10 @@ import androidx.core.graphics.toColorInt
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.config.OldThemeConfig import io.legado.app.help.config.OldThemeConfig
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.getClipText import io.legado.app.utils.getClipText
import io.legado.app.utils.share import io.legado.app.utils.share
@@ -48,6 +48,7 @@ import io.legado.app.utils.toastOnUi
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ThemeListDialog( fun ThemeListDialog(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -55,7 +56,8 @@ fun ThemeListDialog(
var deleteIndex by remember { mutableStateOf<Int?>(null) } var deleteIndex by remember { mutableStateOf<Int?>(null) }
val themeList = remember(listVersion) { OldThemeConfig.configList.toList() } val themeList = remember(listVersion) { OldThemeConfig.configList.toList() }
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Column( Column(
@@ -69,9 +71,9 @@ fun ThemeListDialog(
.padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp), .padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( AppText(
text = stringResource(R.string.theme_list), text = stringResource(R.string.theme_list),
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
IconButton( IconButton(
@@ -104,13 +106,11 @@ fun ThemeListDialog(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors( containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) { MaterialTheme.colorScheme.secondaryContainer
MaterialTheme.colorScheme.secondaryContainer } else {
} else { MaterialTheme.colorScheme.surfaceContainer
MaterialTheme.colorScheme.surfaceContainer }
}
)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -129,9 +129,9 @@ fun ThemeListDialog(
) )
.padding(8.dp) .padding(8.dp)
) )
Text( AppText(
text = item.themeName, text = item.themeName,
style = MaterialTheme.typography.bodyLarge, style = LegadoTheme.typography.bodyLarge,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
IconButton( IconButton(
@@ -161,8 +161,8 @@ fun ThemeListDialog(
deleteIndex?.let { index -> deleteIndex?.let { index ->
AlertDialog( AlertDialog(
onDismissRequest = { deleteIndex = null }, onDismissRequest = { deleteIndex = null },
title = { Text(stringResource(R.string.delete)) }, title = { AppText(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.sure_del)) }, text = { AppText(stringResource(R.string.sure_del)) },
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
@@ -171,12 +171,12 @@ fun ThemeListDialog(
deleteIndex = null deleteIndex = null
} }
) { ) {
Text(stringResource(android.R.string.ok)) AppText(stringResource(android.R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { deleteIndex = null }) { 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.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -37,7 +36,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.DictRule import io.legado.app.data.entities.DictRule
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -56,7 +56,8 @@ fun DictRuleEditSheet(
var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") } var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") }
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
GlassModalBottomSheet( AppModalBottomSheet(
show = true,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Box( Box(
@@ -69,7 +70,7 @@ fun DictRuleEditSheet(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = { Text(stringResource(R.string.dict_rule)) }, title = { AppText(stringResource(R.string.dict_rule)) },
navigationIcon = { navigationIcon = {
IconButton(onClick = onDismissRequest) { IconButton(onClick = onDismissRequest) {
Icon( Icon(
@@ -87,7 +88,7 @@ fun DictRuleEditSheet(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.copy_rule)) }, text = { AppText(stringResource(R.string.copy_rule)) },
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) }, leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
onClick = { onClick = {
onCopy( onCopy(
@@ -102,7 +103,7 @@ fun DictRuleEditSheet(
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.paste_rule)) }, text = { AppText(stringResource(R.string.paste_rule)) },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) }, leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = { onClick = {
scope.launch { scope.launch {
@@ -132,20 +133,20 @@ fun DictRuleEditSheet(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { Text(stringResource(R.string.name)) }, label = { AppText(stringResource(R.string.name)) },
singleLine = true singleLine = true
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = urlRule, value = urlRule,
onValueChange = { urlRule = it }, onValueChange = { urlRule = it },
label = { Text(stringResource(R.string.url_rule)) } label = { AppText(stringResource(R.string.url_rule)) }
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = showRule, value = showRule,
onValueChange = { showRule = it }, onValueChange = { showRule = it },
label = { Text(stringResource(R.string.show_rule)) }, label = { AppText(stringResource(R.string.show_rule)) },
minLines = 3 minLines = 3
) )
} }
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable 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.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
@@ -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.RuleEditFields
import io.legado.app.ui.widget.components.rules.RuleEditSheet 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.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -153,54 +150,49 @@ fun DictRuleScreen(
) )
} }
if (showExportSheet) { FilePickerSheet(
FilePickerSheet( show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export), title = stringResource(R.string.export),
onSelectSysDir = { onSelectSysDir = {
showExportSheet = false showExportSheet = false
exportDoc.launch("exportDictRule.json") exportDoc.launch("exportDictRule.json")
}, },
onUpload = { onUpload = {
showExportSheet = false showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules) viewModel.uploadSelectedRules(selectedIds, rules)
}, },
allowExtensions = arrayOf("json") 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 -> FilePickerSheet(
BatchImportDialog( show = showImportSheet,
title = "导入词典规则", onDismissRequest = { showImportSheet = false },
importState = state, title = stringResource(R.string.import_dict_rule),
onDismissRequest = { viewModel.cancelImport() }, onSelectSysFile = { types ->
onToggleItem = { viewModel.toggleImportSelection(it) }, importDoc.launch(types)
onToggleAll = { viewModel.toggleImportAll(it) }, showImportSheet = false
onConfirm = { viewModel.saveImportedRules() }, },
itemContent = { rule, _ -> onManualInput = {
Column { showUrlInput = true
Text(rule.name, style = MaterialTheme.typography.titleMedium) showImportSheet = false
Text(rule.urlRule, style = MaterialTheme.typography.bodySmall, maxLines = 1) },
} 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) { LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) {
@@ -211,16 +203,16 @@ fun DictRuleScreen(
showDeleteRuleDialog?.let { rule -> showDeleteRuleDialog?.let { rule ->
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null }, onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) }, title = { AppText(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) }, text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
viewModel.delete(rule); showDeleteRuleDialog = null viewModel.delete(rule); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) } }) { AppText(stringResource(R.string.ok)) }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) { TextButton(onClick = { showDeleteRuleDialog = null }) {
Text( AppText(
stringResource(R.string.cancel) stringResource(R.string.cancel)
) )
} }
@@ -228,47 +220,46 @@ fun DictRuleScreen(
) )
} }
if (showEditSheet) { RuleEditSheet(
RuleEditSheet( show = showEditSheet,
rule = editingRule, rule = editingRule,
title = stringResource(R.string.dict_rule), title = stringResource(R.string.dict_rule),
label1 = stringResource(R.string.url_rule), label1 = stringResource(R.string.url_rule),
label2 = stringResource(R.string.show_rule), label2 = stringResource(R.string.show_rule),
onDismissRequest = { onDismissRequest = {
showEditSheet = false showEditSheet = false
editingRule = null editingRule = null
}, },
onSave = { updatedRule -> onSave = { updatedRule ->
if (editingRule == null) { if (editingRule == null) {
viewModel.insert(updatedRule) viewModel.insert(updatedRule)
} else { } else {
viewModel.update(updatedRule) 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
)
} }
) 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( RuleListScaffold(
title = "字典规则", title = "字典规则",
@@ -310,7 +301,7 @@ fun DictRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_str)) }, text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
@@ -181,7 +181,7 @@ open class MainActivity : BaseComposeActivity() {
(slideIntoContainer( (slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start, towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween( animationSpec = tween(
durationMillis = 520, durationMillis = 480,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
), ),
initialOffset = { fullWidth -> fullWidth } initialOffset = { fullWidth -> fullWidth }
@@ -193,7 +193,7 @@ open class MainActivity : BaseComposeActivity() {
)) togetherWith (slideOutOfContainer( )) togetherWith (slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start, towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween( animationSpec = tween(
durationMillis = 520, durationMillis = 480,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
), ),
targetOffset = { fullWidth -> fullWidth / 4 } targetOffset = { fullWidth -> fullWidth / 4 }
@@ -208,7 +208,7 @@ open class MainActivity : BaseComposeActivity() {
(slideIntoContainer( (slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Start, towards = AnimatedContentTransitionScope.SlideDirection.Start,
animationSpec = tween( animationSpec = tween(
durationMillis = 520, durationMillis = 480,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
), ),
initialOffset = { fullWidth -> -fullWidth / 4 } initialOffset = { fullWidth -> -fullWidth / 4 }
@@ -220,7 +220,7 @@ open class MainActivity : BaseComposeActivity() {
)) togetherWith (scaleOut( )) togetherWith (scaleOut(
targetScale = 0.8f, targetScale = 0.8f,
animationSpec = tween( animationSpec = tween(
durationMillis = 520, durationMillis = 480,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
) )
) + fadeOut( ) + fadeOut(
@@ -12,42 +12,57 @@ import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.RssFeed import androidx.compose.material.icons.outlined.RssFeed
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.R 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( sealed class MainDestination(
val route: String, val route: String,
@StringRes val labelId: Int, @StringRes val labelId: Int,
val icon: ImageVector, val m3Icon: ImageVector,
val selectedIcon: ImageVector val m3SelectedIcon: ImageVector,
val miuixIcon: ImageVector,
val miuixSelectedIcon: ImageVector
) { ) {
object Bookshelf : MainDestination( object Bookshelf : MainDestination(
"bookshelf", route = "bookshelf",
R.string.bookshelf, labelId = R.string.bookshelf,
Icons.AutoMirrored.Outlined.LibraryBooks, m3Icon = Icons.AutoMirrored.Outlined.LibraryBooks,
Icons.AutoMirrored.Filled.LibraryBooks m3SelectedIcon = Icons.AutoMirrored.Filled.LibraryBooks,
miuixIcon = MiuixIcons.Regular.ContactsBook, // 替换为实际的 MIUIX 线框图标
miuixSelectedIcon = MiuixIcons.Heavy.ContactsBook // 替换为实际的 MIUIX 填充图标
) )
object Explore : MainDestination( object Explore : MainDestination(
"explore", route = "explore",
R.string.discovery, labelId = R.string.discovery,
Icons.Outlined.Explore, m3Icon = Icons.Outlined.Explore,
Icons.Default.Explore m3SelectedIcon = Icons.Default.Explore,
miuixIcon = MiuixIcons.Regular.Album,
miuixSelectedIcon = MiuixIcons.Heavy.Album
) )
object Rss : MainDestination( object Rss : MainDestination(
"rss", route = "rss",
R.string.rss, labelId = R.string.rss,
Icons.Outlined.RssFeed, m3Icon = Icons.Outlined.RssFeed,
Icons.Default.RssFeed m3SelectedIcon = Icons.Default.RssFeed,
miuixIcon = MiuixIcons.Regular.Favorites,
miuixSelectedIcon = MiuixIcons.Heavy.Favorites
) )
object My : MainDestination( object My : MainDestination(
"my", route = "my",
R.string.my, labelId = R.string.my,
Icons.Outlined.Person, m3Icon = Icons.Outlined.Person,
Icons.Default.Person m3SelectedIcon = Icons.Default.Person,
miuixIcon = MiuixIcons.Regular.Settings,
miuixSelectedIcon = MiuixIcons.Heavy.Settings
) )
companion object { companion object {
val mainDestinations = listOf(Bookshelf, Explore, Rss, My) 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.material.icons.filled.Search
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox import androidx.compose.material3.BadgedBox
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.WideNavigationRail import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem import androidx.compose.material3.WideNavigationRailItem
@@ -56,17 +52,19 @@ import io.legado.app.R
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.mainConfig.MainConfig 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.BookshelfScreen
import io.legado.app.ui.main.bookshelf.BookshelfViewModel import io.legado.app.ui.main.bookshelf.BookshelfViewModel
import io.legado.app.ui.main.explore.ExploreScreen import io.legado.app.ui.main.explore.ExploreScreen
import io.legado.app.ui.main.my.MyScreen import io.legado.app.ui.main.my.MyScreen
import io.legado.app.ui.main.rss.RssScreen import io.legado.app.ui.main.rss.RssScreen
import io.legado.app.ui.theme.regularHazeEffect 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.AppScaffold
import io.legado.app.ui.widget.components.GlassDefaults 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.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.startActivityForBook import io.legado.app.utils.startActivityForBook
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -151,7 +149,7 @@ fun MainScreen(
onClick = { context.startActivity<SearchActivity>() }, onClick = { context.startActivity<SearchActivity>() },
expanded = expanded, expanded = expanded,
icon = { Icon(Icons.Default.Search, contentDescription = null) }, 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 -> ) { dismiss ->
bookshelfUiState.groups.forEachIndexed { groupIndex, group -> bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(group.groupName) }, text = group.groupName,
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
if (pagerState.currentPage != index) { if (pagerState.currentPage != index) {
@@ -224,7 +222,7 @@ fun MainScreen(
} }
}, },
label = if (labelVisibilityMode != "unlabeled") { label = if (labelVisibilityMode != "unlabeled") {
{ Text(stringResource(destination.labelId)) } { AppText(stringResource(destination.labelId)) }
} else null } else null
) )
} }
@@ -237,103 +235,38 @@ fun MainScreen(
if (!useRail && MainConfig.showBottomView) { if (!useRail && MainConfig.showBottomView) {
val labelVisibilityMode = MainConfig.labelVisibilityMode val labelVisibilityMode = MainConfig.labelVisibilityMode
val isUnlabeled = labelVisibilityMode == "unlabeled" val isUnlabeled = labelVisibilityMode == "unlabeled"
NavigationBar(
AppNavigationBar(
modifier = Modifier modifier = Modifier
.regularHazeEffect(state = hazeState) .regularHazeEffect(state = hazeState)
.height(if (isUnlabeled) 64.dp else 80.dp), .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))
}
) { ) {
val alwaysShowLabel = when (labelVisibilityMode) { val alwaysShowLabel = when (labelVisibilityMode) {
"labeled" -> true "labeled" -> true
"selected" -> false
"unlabeled" -> false
else -> false else -> false
} }
destinations.forEachIndexed { index, destination -> destinations.forEachIndexed { index, destination ->
val selected = pagerState.targetPage == index val selected = pagerState.targetPage == index
var showGroupMenu by remember { mutableStateOf(false) } AppNavigationBarItem(
val haptic = LocalHapticFeedback.current
NavigationBarItem(
selected = selected, selected = selected,
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch { pagerState.animateScrollToPage(index) }
pagerState.animateScrollToPage(index)
}
}, },
icon = { labelString = stringResource(destination.labelId),
Box { miuixIcon = if (selected) destination.miuixSelectedIcon else destination.miuixIcon,
NavigationIcon( m3Icon = {
destination = destination, NavigationIcon(
selected = selected, destination = destination,
upBooksCount = uiState.upBooksCount, selected = selected,
modifier = if (destination == MainDestination.Bookshelf) { upBooksCount = uiState.upBooksCount
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)
)
}
}
)
}
}
}
}
}, },
colors = NavigationBarItemDefaults.colors( m3IndicatorColor = GlassDefaults.glassColor(
indicatorColor = GlassDefaults.glassColor( noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
noBlurColor = MaterialTheme.colorScheme.secondaryContainer, blurAlpha = GlassDefaults.ThickBlurAlpha
blurAlpha = GlassDefaults.ThickBlurAlpha
),
), ),
label = if (labelVisibilityMode != "unlabeled") { m3ShowLabel = !isUnlabeled,
{ Text(stringResource(destination.labelId)) } m3AlwaysShowLabel = alwaysShowLabel
} else null,
alwaysShowLabel = alwaysShowLabel
) )
} }
} }
@@ -386,7 +319,7 @@ private fun NavigationIcon(
upBooksCount: Int, upBooksCount: Int,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val icon = if (selected) destination.selectedIcon else destination.icon val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon
Box(modifier = modifier) { Box(modifier = modifier) {
if (destination == MainDestination.Bookshelf && upBooksCount > 0) { if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) { 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.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -34,9 +33,11 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.cover.BookCover 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.BookshelfCover
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toTimeAgo import io.legado.app.utils.toTimeAgo
/** /**
@@ -88,9 +89,9 @@ fun BookshelfItem(
) { ) {
cover(Modifier.fillMaxSize()) cover(Modifier.fillMaxSize())
if (gridStyle == 1) { if (gridStyle == 1) {
Text( AppText(
text = title, 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, color = Color.White,
shadow = Shadow( shadow = Shadow(
color = Color.Black.copy(alpha = 0.5f), color = Color.Black.copy(alpha = 0.5f),
@@ -117,9 +118,9 @@ fun BookshelfItem(
} }
if (gridStyle == 0) { if (gridStyle == 0) {
Text( AppText(
text = title, text = title,
style = if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium, style = if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium,
maxLines = titleMaxLines, maxLines = titleMaxLines,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start, textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
@@ -152,16 +153,16 @@ fun BookshelfItem(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Text( AppText(
text = title, text = title,
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
maxLines = if (!isCompact) 2 else 1, maxLines = if (!isCompact) 2 else 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
subTitle?.let { subTitle?.let {
Text( AppText(
text = it, text = it,
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(top = 2.dp) modifier = Modifier.padding(top = 2.dp)
@@ -169,9 +170,9 @@ fun BookshelfItem(
} }
if (!isCompact) { if (!isCompact) {
desc?.let { desc?.let {
Text( AppText(
text = it, text = it,
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(top = 2.dp) modifier = Modifier.padding(top = 2.dp)
@@ -372,7 +373,7 @@ fun BookItem(
isUpdating = isUpdating, isUpdating = isUpdating,
modifier = modifier, modifier = modifier,
badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null, 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, title = book.name,
@@ -384,16 +385,16 @@ fun BookItem(
desc = stringResource(R.string.read_dur_progress, book.durChapterTitle ?: ""), desc = stringResource(R.string.read_dur_progress, book.durChapterTitle ?: ""),
extra = { extra = {
if (BookshelfConfig.showLastUpdateTime && !book.isLocal) { if (BookshelfConfig.showLastUpdateTime && !book.isLocal) {
Text( AppText(
text = book.latestChapterTime.toTimeAgo(), text = book.latestChapterTime.toTimeAgo(),
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary, color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(end = 4.dp) modifier = Modifier.padding(end = 4.dp)
) )
} }
Text( AppText(
text = book.latestChapterTitle ?: "", text = book.latestChapterTitle ?: "",
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
@@ -16,6 +16,7 @@ data class BookShelfItem(
val durChapterPos: Int, val durChapterPos: Int,
val latestChapterTitle: String?, val latestChapterTitle: String?,
val latestChapterTime: Long, val latestChapterTime: Long,
val lastCheckCount: Int,
val totalChapterNum: Int, val totalChapterNum: Int,
val durChapterIndex: Int, val durChapterIndex: Int,
val type: Int, val type: Int,
@@ -33,5 +34,8 @@ data class BookShelfItem(
val isNotShelf: Boolean get() = (type and BookType.notShelf) > 0 val isNotShelf: Boolean get() = (type and BookType.notShelf) > 0
val isNew: Boolean get() = lastCheckCount > 0
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 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.material.icons.filled.ViewCompact
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
@@ -32,7 +31,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig 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.CompactDropdownSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
@@ -40,28 +40,28 @@ import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookshelfConfigSheet( fun BookshelfConfigSheet(
show: Boolean,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(
title = stringResource(R.string.bookshelf_layout),
show = show,
onDismissRequest = onDismissRequest
) {
GlassCard() {
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.animateContentSize() .animateContentSize()
.padding(horizontal = 16.dp)
.padding(bottom = 32.dp), .padding(bottom = 32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text(
text = stringResource(R.string.bookshelf_layout),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 16.dp)
)
// Group Style
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = stringResource(R.string.group_style), title = stringResource(R.string.group_style),
selectedValue = BookshelfConfig.bookGroupStyle.toString(), selectedValue = BookshelfConfig.bookGroupStyle.toString(),
@@ -40,12 +40,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.pullToRefresh import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -60,13 +57,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import io.legado.app.R import io.legado.app.R
@@ -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.manage.BookshelfManageActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton 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.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog 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.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.readText import io.legado.app.utils.readText
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -237,37 +235,37 @@ fun BookshelfScreen(
topBarActions = { }, topBarActions = { },
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.add_remote_book)) }, text = stringResource(R.string.add_remote_book),
onClick = { context.startActivity<RemoteBookActivity>(); dismiss() }, onClick = { context.startActivity<RemoteBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Wifi, null) } leadingIcon = { Icon(Icons.Default.Wifi, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.book_local)) }, text = stringResource(R.string.book_local),
onClick = { context.startActivity<ImportBookActivity>(); dismiss() }, onClick = { context.startActivity<ImportBookActivity>(); dismiss() },
leadingIcon = { Icon(Icons.Default.Save, null) } leadingIcon = { Icon(Icons.Default.Save, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.update_toc)) }, text = stringResource(R.string.update_toc),
onClick = { viewModel.upToc(uiState.items); dismiss() }, onClick = { viewModel.upToc(uiState.items); dismiss() },
leadingIcon = { Icon(Icons.Default.Refresh, null) } leadingIcon = { Icon(Icons.Default.Refresh, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("布局设置") }, text = "布局设置",
onClick = { showConfigSheet = true; dismiss() }, onClick = { showConfigSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.GridView, null) } leadingIcon = { Icon(Icons.Default.GridView, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.group_manage)) }, text = stringResource(R.string.group_manage),
onClick = { showGroupManageSheet = true; dismiss() }, onClick = { showGroupManageSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Edit, null) } leadingIcon = { Icon(Icons.Default.Edit, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.add_url)) }, text = stringResource(R.string.add_url),
onClick = { showAddUrlDialog = true; dismiss() }, onClick = { showAddUrlDialog = true; dismiss() },
leadingIcon = { Icon(Icons.Default.Link, null) } leadingIcon = { Icon(Icons.Default.Link, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.bookshelf_management)) }, text = stringResource(R.string.bookshelf_management),
onClick = { onClick = {
val groupId = val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -279,7 +277,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Settings, null) } leadingIcon = { Icon(Icons.Default.Settings, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.cache_export)) }, text = stringResource(R.string.cache_export),
onClick = { onClick = {
val groupId = val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
@@ -291,7 +289,7 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.Download, null) } leadingIcon = { Icon(Icons.Default.Download, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.export_bookshelf)) }, text = stringResource(R.string.export_bookshelf),
onClick = { onClick = {
showExportSheet = true showExportSheet = true
dismiss() dismiss()
@@ -299,12 +297,12 @@ fun BookshelfScreen(
leadingIcon = { Icon(Icons.Default.ImportExport, null) } leadingIcon = { Icon(Icons.Default.ImportExport, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_bookshelf)) }, text = stringResource(R.string.import_bookshelf),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.log)) }, text = stringResource(R.string.log),
onClick = { onClick = {
showLogSheet = true showLogSheet = true
dismiss() dismiss()
@@ -322,33 +320,19 @@ fun BookshelfScreen(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
PrimaryScrollableTabRow( val tabTitles = remember(uiState.groups) {
selectedTabIndex = selectedTabIndex, uiState.groups.map { it.groupName }
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
)
}
)
}
} }
AppTabRow(
tabTitles = tabTitles,
selectedTabIndex = selectedTabIndex,
onTabSelected = { index ->
scope.launch { pagerState.animateScrollToPage(index) }
},
modifier = Modifier.weight(1f)
)
if (BookshelfConfig.shouldShowExpandButton) { if (BookshelfConfig.shouldShowExpandButton) {
Box(modifier = Modifier.padding(end = 16.dp)) { Box(modifier = Modifier.padding(end = 16.dp)) {
SmallOutlinedIconToggleButton( SmallOutlinedIconToggleButton(
@@ -363,7 +347,7 @@ fun BookshelfScreen(
) { dismiss -> ) { dismiss ->
uiState.groups.forEachIndexed { index, group -> uiState.groups.forEachIndexed { index, group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(group.groupName) }, text = group.groupName,
onClick = { onClick = {
scope.launch { pagerState.animateScrollToPage(index) } scope.launch { pagerState.animateScrollToPage(index) }
dismiss() dismiss()
@@ -502,13 +486,15 @@ fun BookshelfScreen(
} }
} }
if (showConfigSheet) { BookshelfConfigSheet(
BookshelfConfigSheet(onDismissRequest = { showConfigSheet = false }) show = showConfigSheet,
} onDismissRequest = { showConfigSheet = false }
)
if (showGroupManageSheet) { GroupManageSheet(
GroupManageSheet(onDismissRequest = { showGroupManageSheet = false }) show = showGroupManageSheet,
} onDismissRequest = { showGroupManageSheet = false }
)
if (showAddUrlDialog) { if (showAddUrlDialog) {
SourceInputDialog( SourceInputDialog(
@@ -521,40 +507,39 @@ fun BookshelfScreen(
) )
} }
if (showImportSheet) { FilePickerSheet(
FilePickerSheet( show = showImportSheet,
onDismissRequest = { showImportSheet = false }, onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_bookshelf), title = stringResource(R.string.import_bookshelf),
onSelectSysFile = { types -> onSelectSysFile = { types ->
importLauncher.launch(types) importLauncher.launch(types)
showImportSheet = false showImportSheet = false
}, },
onManualInput = { onManualInput = {
showAddUrlDialog = true showAddUrlDialog = true
showImportSheet = false showImportSheet = false
}, },
allowExtensions = arrayOf("json", "txt") allowExtensions = arrayOf("json", "txt")
) )
}
if (showExportSheet) { FilePickerSheet(
FilePickerSheet( show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
title = stringResource(R.string.export_bookshelf), title = stringResource(R.string.export_bookshelf),
onSelectSysDir = { onSelectSysDir = {
showExportSheet = false showExportSheet = false
exportLauncher.launch("bookshelf.json") exportLauncher.launch("bookshelf.json")
}, },
onUpload = { onUpload = {
showExportSheet = false showExportSheet = false
viewModel.uploadBookshelf(uiState.items) viewModel.uploadBookshelf(uiState.items)
} }
) )
}
if (showLogSheet) { AppLogSheet(
AppLogSheet(onDismissRequest = { showLogSheet = false }) show = showLogSheet,
} onDismissRequest = { showLogSheet = false }
)
if (uiState.isLoading) { if (uiState.isLoading) {
Dialog(onDismissRequest = {}) { Dialog(onDismissRequest = {}) {
@@ -568,10 +553,10 @@ fun BookshelfScreen(
) { ) {
CircularProgressIndicator() CircularProgressIndicator()
uiState.loadingText?.let { uiState.loadingText?.let {
Text( AppText(
text = it, text = it,
modifier = Modifier.padding(top = 16.dp), 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.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -27,7 +21,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -36,8 +29,9 @@ import io.legado.app.R
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.book.group.GroupEditContent import io.legado.app.ui.book.group.GroupEditContent
import io.legado.app.ui.book.group.GroupViewModel import io.legado.app.ui.book.group.GroupViewModel
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.move import io.legado.app.utils.move
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -45,6 +39,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupManageSheet( fun GroupManageSheet(
show: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
viewModel: GroupViewModel = koinViewModel(), viewModel: GroupViewModel = koinViewModel(),
bookshelfViewModel: BookshelfViewModel = koinViewModel() bookshelfViewModel: BookshelfViewModel = koinViewModel()
@@ -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( AnimatedContent(
targetState = isEditing, targetState = isEditing,
transitionSpec = { transitionSpec = {
@@ -92,56 +102,28 @@ fun GroupManageSheet(
viewModel = viewModel viewModel = viewModel
) )
} else { } else {
Column( LazyColumn(
modifier = Modifier state = listState,
.fillMaxWidth() modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Row( items(listData, key = { it.groupId }) { group ->
modifier = Modifier val manageNameInfo = remember(group) { group.getManageName(context) }
.fillMaxWidth() ReorderableSelectionItem(
.padding(horizontal = 16.dp) state = reorderableState,
.padding(vertical = 8.dp), key = group.groupId,
horizontalArrangement = Arrangement.SpaceBetween, title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
verticalAlignment = Alignment.CenterVertically subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
) { isEnabled = group.show,
Text( containerColor = MaterialTheme.colorScheme.surface,
text = stringResource(R.string.group_manage), onEnabledChange = { isChecked ->
style = MaterialTheme.typography.titleLarge 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.ListItemDefaults
import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider 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.search.SearchScope
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.login.SourceLoginActivity 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.GlassCard
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider 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.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import top.yukonga.miuix.kmp.theme.MiuixTheme
import kotlin.math.roundToInt import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@@ -138,6 +141,8 @@ fun ExploreScreen(
} }
} }
val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
ListScaffold( ListScaffold(
title = stringResource(R.string.discovery), title = stringResource(R.string.discovery),
state = uiState, state = uiState,
@@ -148,13 +153,13 @@ fun ExploreScreen(
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) }, leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { Text(stringResource(R.string.all)) }, text = stringResource(R.string.all),
onClick = { viewModel.setGroup(""); dismiss() } onClick = { viewModel.setGroup(""); dismiss() }
) )
uiState.groups.forEach { group -> uiState.groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { Text(group) }, text = group,
onClick = { viewModel.setGroup(group); dismiss() } onClick = { viewModel.setGroup(group); dismiss() }
) )
} }
@@ -197,7 +202,8 @@ fun ExploreScreen(
} }
}, },
onRefresh = { viewModel.refreshExploreKinds(item) }, onRefresh = { viewModel.refreshExploreKinds(item) },
onDelete = { sourceToDelete = item } onDelete = { sourceToDelete = item },
isMiuix = composeEngine
) )
} }
@@ -228,7 +234,8 @@ fun ExploreScreen(
putExtra("exploreUrl", kind.url) putExtra("exploreUrl", kind.url)
} }
} }
} },
isMiuix = composeEngine
) )
} }
@@ -264,7 +271,8 @@ fun ExploreScreen(
uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl } uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl }
if (index >= 0) listState.animateScrollToItem(index) if (index >= 0) listState.animateScrollToItem(index)
} }
} },
isMiuix = composeEngine
) )
} }
} }
@@ -274,19 +282,19 @@ fun ExploreScreen(
sourceToDelete?.let { source -> sourceToDelete?.let { source ->
AlertDialog( AlertDialog(
onDismissRequest = { sourceToDelete = null }, onDismissRequest = { sourceToDelete = null },
title = { Text(stringResource(R.string.draw)) }, title = { AppText(stringResource(R.string.draw)) },
text = { Text(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) }, text = { AppText(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
confirmButton = { confirmButton = {
TextButton(onClick = { TextButton(onClick = {
viewModel.deleteSource(source) viewModel.deleteSource(source)
sourceToDelete = null sourceToDelete = null
}) { }) {
Text(stringResource(R.string.yes)) AppText(stringResource(R.string.yes))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { sourceToDelete = null }) { TextButton(onClick = { sourceToDelete = null }) {
Text(stringResource(R.string.no)) AppText(stringResource(R.string.no))
} }
} }
) )
@@ -341,14 +349,25 @@ fun ExploreSourceHeader(
onLogin: () -> Unit, onLogin: () -> Unit,
onRefresh: () -> Unit, onRefresh: () -> Unit,
onDelete: () -> Unit, onDelete: () -> Unit,
isMiuix: Boolean,
) { ) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(if (isExpanded) 90f else 0f, label = "rotation") val rotation by animateFloatAsState(if (isExpanded) 90f else 0f, label = "rotation")
val containerColor by animateColorAsState( val containerColor by animateColorAsState(
targetValue = if (isExpanded) targetValue = if (isExpanded)
MaterialTheme.colorScheme.secondaryContainer if (isMiuix) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
else 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), animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
label = "CardColor" label = "CardColor"
) )
@@ -358,7 +377,7 @@ fun ExploreSourceHeader(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp), .padding(horizontal = 16.dp, vertical = 4.dp),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors(containerColor = containerColor), containerColor = containerColor
) { ) {
ListItem( ListItem(
modifier = Modifier modifier = Modifier
@@ -371,14 +390,10 @@ fun ExploreSourceHeader(
containerColor = Color.Transparent containerColor = Color.Transparent
), ),
headlineContent = { headlineContent = {
Text( AppText(
text = item.bookSourceName, text = item.bookSourceName,
style = MaterialTheme.typography.titleMedium, style = LegadoTheme.typography.titleMedium,
color = if (isExpanded) { color = contentColor
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
}
) )
}, },
trailingContent = { trailingContent = {
@@ -401,29 +416,29 @@ fun ExploreSourceHeader(
PillHeaderDivider(title = item.bookSourceName) PillHeaderDivider(title = item.bookSourceName)
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) }, leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { Text(stringResource(R.string.to_top)) }, text = stringResource(R.string.to_top),
onClick = { onTop(); showMenu = false } onClick = { onTop(); showMenu = false }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) }, leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { Text(stringResource(R.string.edit)) }, text = stringResource(R.string.edit),
onClick = { onEdit(); showMenu = false } onClick = { onEdit(); showMenu = false }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Search) }, leadingIcon = { MenuItemIcon(Icons.Default.Search) },
text = { Text(stringResource(R.string.search)) }, text = stringResource(R.string.search),
onClick = { onSearch(); showMenu = false } onClick = { onSearch(); showMenu = false }
) )
if (item.hasLoginUrl) { if (item.hasLoginUrl) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { Text(stringResource(R.string.login)) }, text = stringResource(R.string.login),
onClick = { onLogin(); showMenu = false } onClick = { onLogin(); showMenu = false }
) )
} }
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Refresh) }, leadingIcon = { MenuItemIcon(Icons.Default.Refresh) },
text = { Text(stringResource(R.string.refresh)) }, text = stringResource(R.string.refresh),
onClick = { onRefresh(); showMenu = false } onClick = { onRefresh(); showMenu = false }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
@@ -433,12 +448,8 @@ fun ExploreSourceHeader(
tint = MaterialTheme.colorScheme.error tint = MaterialTheme.colorScheme.error
) )
}, },
text = { text = stringResource(R.string.delete),
Text( color = LegadoTheme.colorScheme.error,
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
onClick = { onDelete(); showMenu = false } onClick = { onDelete(); showMenu = false }
) )
} }
@@ -450,12 +461,12 @@ fun ExploreSourceHeader(
@Composable @Composable
fun ExploreStickyCard( fun ExploreStickyCard(
item: BookSourcePart, item: BookSourcePart,
onClick: () -> Unit onClick: () -> Unit,
isMiuix: Boolean,
) { ) {
TextCard( TextCard(
text = item.bookSourceName, text = item.bookSourceName,
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh, textStyle = LegadoTheme.typography.labelLarge,
textStyle = MaterialTheme.typography.labelLarge,
horizontalPadding = 8.dp, horizontalPadding = 8.dp,
verticalPadding = 6.dp, verticalPadding = 6.dp,
onClick = onClick onClick = onClick
@@ -467,8 +478,29 @@ fun ExploreKindItem(
kind: ExploreKind, kind: ExploreKind,
isClickable: Boolean, isClickable: Boolean,
onClick: () -> Unit, 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( CompositionLocalProvider(
LocalMinimumInteractiveComponentSize provides Dp.Unspecified LocalMinimumInteractiveComponentSize provides Dp.Unspecified
) { ) {
@@ -479,21 +511,19 @@ fun ExploreKindItem(
GlassCard( GlassCard(
onClick = onClick, onClick = onClick,
shape = shape, shape = shape,
colors = CardDefaults.cardColors( containerColor = color.copy(alpha = 0.6f),
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.6f), contentColor = contentColor,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
),
modifier = modifier modifier = modifier
) { ) {
KindText(kind) KindText(kind)
} }
} else { } else {
OutlinedCard( GlassCard(
shape = shape, shape = shape,
colors = CardDefaults.outlinedCardColors( containerColor = unClickBackColor.copy(alpha = 0.6f),
contentColor = MaterialTheme.colorScheme.primary contentColor = unClickColor,
), modifier = modifier,
modifier = modifier border = CardDefaults.outlinedCardBorder()
) { ) {
KindText(kind) KindText(kind)
} }
@@ -506,12 +536,12 @@ fun ExploreKindItem(
private fun KindText( private fun KindText(
kind: ExploreKind kind: ExploreKind
) { ) {
Text( AppText(
text = kind.title, text = kind.title,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp), .padding(vertical = 8.dp),
style = MaterialTheme.typography.labelMediumEmphasized, style = LegadoTheme.typography.labelMediumEmphasized,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1 maxLines = 1
@@ -1,13 +1,20 @@
package io.legado.app.ui.main.my 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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons 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.LibraryBooks
import androidx.compose.material.icons.automirrored.filled.Rule import androidx.compose.material.icons.automirrored.filled.Rule
import androidx.compose.material.icons.filled.Bookmark 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.FindReplace
import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.Folder
import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Info 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.Settings
import androidx.compose.material.icons.filled.Source import androidx.compose.material.icons.filled.Source
import androidx.compose.material.icons.filled.Web import androidx.compose.material.icons.filled.Web
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue 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.dict.rule.DictRuleActivity
import io.legado.app.ui.file.FileManageActivity import io.legado.app.ui.file.FileManageActivity
import io.legado.app.ui.replace.ReplaceRuleActivity 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.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.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) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -70,11 +79,7 @@ fun MyScreen(
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top), .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { title = stringResource(R.string.my),
Text(
text = stringResource(R.string.my)
)
},
actions = { actions = {
IconButton( IconButton(
onClick = { onNavigate(PrefClickEvent.ShowMd("appHelp", "xxx")) } onClick = { onNavigate(PrefClickEvent.ShowMd("appHelp", "xxx")) }
@@ -99,52 +104,21 @@ fun MyScreen(
) )
SplicedColumnGroup( SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.adaptiveHorizontalPadding(),
title = "" title = ""
) { ) {
SettingItem( WebServiceSettingBlock(
title = stringResource(R.string.web_service), uiState = uiState,
description = if (uiState.isWebServiceRun) viewModel = viewModel,
uiState.webServiceAddress onNavigate = onNavigate
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 = { }
) )
} }
SplicedColumnGroup( SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.adaptiveHorizontalPadding(),
title = "规则" title = "规则"
) { ) {
SettingItem( ClickableSettingItem(
title = stringResource(R.string.book_source_manage), title = stringResource(R.string.book_source_manage),
description = stringResource(R.string.book_source_manage_desc), description = stringResource(R.string.book_source_manage_desc),
imageVector = Icons.Default.Source, imageVector = Icons.Default.Source,
@@ -154,7 +128,7 @@ fun MyScreen(
) )
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.replace_purify), title = stringResource(R.string.replace_purify),
imageVector = Icons.Default.FindReplace, imageVector = Icons.Default.FindReplace,
onClick = { onClick = {
@@ -163,7 +137,7 @@ fun MyScreen(
) )
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.txt_toc_rule), title = stringResource(R.string.txt_toc_rule),
imageVector = Icons.AutoMirrored.Filled.Rule, imageVector = Icons.AutoMirrored.Filled.Rule,
onClick = { onClick = {
@@ -172,7 +146,7 @@ fun MyScreen(
) )
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.dict_rule), title = stringResource(R.string.dict_rule),
imageVector = Icons.AutoMirrored.Filled.LibraryBooks, imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
onClick = { onClick = {
@@ -184,44 +158,44 @@ fun MyScreen(
} }
SplicedColumnGroup( SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.adaptiveHorizontalPadding(),
title = stringResource(R.string.other)) { title = stringResource(R.string.other)) {
SettingItem( ClickableSettingItem(
title = stringResource(R.string.setting), title = stringResource(R.string.setting),
imageVector = Icons.Default.Settings, imageVector = Icons.Default.Settings,
onClick = { onClick = {
onOpenSettings() onOpenSettings()
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.bookmark), title = stringResource(R.string.bookmark),
imageVector = Icons.Default.Bookmark, imageVector = Icons.Default.Bookmark,
onClick = { onClick = {
onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java)) onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java))
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.read_record), title = stringResource(R.string.read_record),
imageVector = Icons.Default.History, imageVector = Icons.Default.History,
onClick = { onClick = {
onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java)) onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.file_manage), title = stringResource(R.string.file_manage),
imageVector = Icons.Default.Folder, imageVector = Icons.Default.Folder,
onClick = { onClick = {
onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java)) onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java))
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.about), title = stringResource(R.string.about),
imageVector = Icons.Default.Info, imageVector = Icons.Default.Info,
onClick = { onClick = {
onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java)) onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java))
} }
) )
SettingItem( ClickableSettingItem(
title = stringResource(R.string.exit), title = stringResource(R.string.exit),
imageVector = Icons.AutoMirrored.Filled.ExitToApp, imageVector = Icons.AutoMirrored.Filled.ExitToApp,
onClick = { 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.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState 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.edit.RssSourceEditActivity
import io.legado.app.ui.rss.source.manage.RssSourceActivity import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.ui.rss.subscription.RuleSubActivity 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.SourceIcon
import io.legado.app.ui.widget.components.button.TopBarActionButton import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.divider.PillDivider 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.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -135,12 +136,12 @@ fun RssScreen(
dismiss() dismiss()
}, },
leadingIcon = { MenuItemIcon(Icons.Default.Settings) }, leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
text = { Text("订阅源管理") } text = "订阅源管理"
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Group) }, leadingIcon = { MenuItemIcon(Icons.Default.Group) },
text = { Text(stringResource(R.string.all)) }, text = stringResource(R.string.all),
onClick = { onClick = {
viewModel.setGroup("") viewModel.setGroup("")
dismiss() dismiss()
@@ -149,7 +150,7 @@ fun RssScreen(
uiState.groups.forEach { group -> uiState.groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
text = { Text(group) }, text = group,
onClick = { onClick = {
viewModel.setGroup(group) viewModel.setGroup(group)
dismiss() dismiss()
@@ -188,8 +189,8 @@ fun RssScreen(
sourceToDelete?.let { source -> sourceToDelete?.let { source ->
AlertDialog( AlertDialog(
onDismissRequest = { sourceToDelete = null }, onDismissRequest = { sourceToDelete = null },
title = { Text(stringResource(R.string.draw)) }, title = { AppText(stringResource(R.string.draw)) },
text = { Text(stringResource(R.string.sure_del) + "\n" + source.sourceName) }, text = { AppText(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
@@ -197,12 +198,12 @@ fun RssScreen(
sourceToDelete = null sourceToDelete = null
} }
) { ) {
Text(stringResource(R.string.yes)) AppText(stringResource(R.string.yes))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { sourceToDelete = null }) { TextButton(onClick = { sourceToDelete = null }) {
Text(stringResource(R.string.no)) AppText(stringResource(R.string.no))
} }
} }
) )
@@ -246,7 +247,7 @@ fun RssSourceGridItem(
PillHeaderDivider(title = source.sourceName) PillHeaderDivider(title = source.sourceName)
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) }, leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
text = { Text(stringResource(R.string.to_top)) }, text = stringResource(R.string.to_top),
onClick = { onClick = {
onTop() onTop()
showMenu = false showMenu = false
@@ -254,7 +255,7 @@ fun RssSourceGridItem(
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Edit) }, leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
text = { Text(stringResource(R.string.edit)) }, text = stringResource(R.string.edit),
onClick = { onClick = {
onEdit() onEdit()
showMenu = false showMenu = false
@@ -263,7 +264,7 @@ fun RssSourceGridItem(
if (!source.loginUrl.isNullOrBlank()) { if (!source.loginUrl.isNullOrBlank()) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
text = { Text(stringResource(R.string.login)) }, text = stringResource(R.string.login),
onClick = { onClick = {
onLogin() onLogin()
showMenu = false showMenu = false
@@ -272,7 +273,7 @@ fun RssSourceGridItem(
} }
RoundDropdownMenuItem( RoundDropdownMenuItem(
leadingIcon = { MenuItemIcon(Icons.Default.Close) }, leadingIcon = { MenuItemIcon(Icons.Default.Close) },
text = { Text(stringResource(R.string.disable_source)) }, text = stringResource(R.string.disable_source),
onClick = { onClick = {
onDisable() onDisable()
showMenu = false showMenu = false
@@ -285,12 +286,8 @@ fun RssSourceGridItem(
tint = MaterialTheme.colorScheme.error tint = MaterialTheme.colorScheme.error
) )
}, },
text = { text = stringResource(R.string.delete),
Text( color = MaterialTheme.colorScheme.error,
stringResource(R.string.delete),
color = MaterialTheme.colorScheme.error
)
},
onClick = { onClick = {
onDelete() onDelete()
showMenu = false showMenu = false
@@ -299,9 +296,9 @@ fun RssSourceGridItem(
} }
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
text = source.sourceName, text = source.sourceName,
style = MaterialTheme.typography.labelMedium, style = LegadoTheme.typography.labelMedium,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
@@ -8,11 +8,9 @@ import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@@ -20,20 +18,15 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material.icons.filled.FileOpen
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
@@ -50,14 +43,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -165,56 +158,48 @@ fun ReplaceRuleScreen(
) )
} }
if (showImportSheet) { FilePickerSheet(
FilePickerSheet( show = showImportSheet,
onDismissRequest = { showImportSheet = false }, onDismissRequest = { showImportSheet = false },
title = stringResource(R.string.import_replace_rule), title = stringResource(R.string.import_replace_rule),
onSelectSysFile = { types -> onSelectSysFile = { types ->
importDoc.launch(types) importDoc.launch(types)
showImportSheet = false showImportSheet = false
}, },
onManualInput = { onManualInput = {
showUrlInput = true showUrlInput = true
showImportSheet = false showImportSheet = false
}, },
allowExtensions = arrayOf("json", "txt") allowExtensions = arrayOf("json", "txt")
) )
}
if (showExportSheet) { FilePickerSheet(
FilePickerSheet( show = showExportSheet,
onDismissRequest = { showExportSheet = false }, onDismissRequest = { showExportSheet = false },
onSelectSysDir = { onSelectSysDir = {
showExportSheet = false showExportSheet = false
exportDoc.launch("exportReplaceRule.json") exportDoc.launch("exportReplaceRule.json")
}, },
onUpload = { onUpload = {
showExportSheet = false showExportSheet = false
viewModel.uploadSelectedRules(selectedIds, rules) viewModel.uploadSelectedRules(selectedIds, rules)
}, },
allowExtensions = arrayOf("json") allowExtensions = arrayOf("json")
) )
}
(importState as? BaseImportUiState.Success<ReplaceRule>)?.let { state -> BatchImportDialog(
BatchImportDialog( title = stringResource(R.string.import_replace_rule),
title = stringResource(R.string.import_replace_rule), importState = importState,
importState = state, onDismissRequest = { viewModel.cancelImport() },
onDismissRequest = { viewModel.cancelImport() }, onToggleItem = { viewModel.toggleImportSelection(it) },
onToggleItem = { viewModel.toggleImportSelection(it) }, onToggleAll = { viewModel.toggleImportAll(it) },
onToggleAll = { viewModel.toggleImportAll(it) }, onConfirm = { viewModel.saveImportedRules() },
onConfirm = { viewModel.saveImportedRules() }, topBarActions = {},
topBarActions = {}, itemTitle = { rule -> rule.name },
itemContent = { rule, _ -> itemSubtitle = { rule ->
Column { rule.group?.takeIf { it.isNotBlank() }
Text(rule.name, style = MaterialTheme.typography.titleMedium) }
if (!rule.group.isNullOrBlank()) { )
Text(rule.group!!, style = MaterialTheme.typography.bodySmall)
}
}
}
)
}
if (importState is BaseImportUiState.Loading) { if (importState is BaseImportUiState.Loading) {
Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() } Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() }
@@ -267,28 +252,29 @@ fun ReplaceRuleScreen(
} }
} }
if (showGroupManageSheet) {
GroupManageBottomSheet( GroupManageBottomSheet(
groups = groups, show = showGroupManageSheet,
onDismissRequest = { showGroupManageSheet = false }, groups = groups,
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) }, onDismissRequest = { showGroupManageSheet = false },
onDeleteGroup = { viewModel.delGroup(it) } onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
) onDeleteGroup = { viewModel.delGroup(it) }
} )
showDeleteRuleDialog?.let { rule -> showDeleteRuleDialog?.let { rule ->
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null }, onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) }, title = { AppText(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) }, text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
viewModel.delete(rule); showDeleteRuleDialog = null viewModel.delete(rule); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) } }) { AppText(stringResource(R.string.ok)) }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) { TextButton(onClick = { showDeleteRuleDialog = null }) {
Text( AppText(
stringResource(R.string.cancel) stringResource(R.string.cancel)
) )
} }
@@ -347,39 +333,21 @@ fun ReplaceRuleScreen(
}, },
bottomContent = { bottomContent = {
if (tabItems.size > 1) { if (tabItems.size > 1) {
PrimaryScrollableTabRow( AppTabRow(
selectedTabIndex = selectedTabIndex.coerceAtMost(tabItems.size - 1) tabTitles = tabItems,
.coerceAtLeast(0), selectedTabIndex = selectedTabIndex,
edgePadding = 0.dp, onTabSelected = { index ->
divider = {}, selectedTabIndex = index
containerColor = Color.Transparent, viewModel.setGroup(tabItems[index])
) {
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
)
}
)
} }
} )
} }
}, },
floatingActionButton = { floatingActionButton = {
TooltipBox( TooltipBox(
positionProvider = positionProvider =
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text("Localized description") } }, tooltip = { PlainTooltip { AppText("Localized description") } },
state = rememberTooltipState(), state = rememberTooltipState(),
) { ) {
with(sharedTransitionScope) { with(sharedTransitionScope) {
@@ -406,29 +374,29 @@ fun ReplaceRuleScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_str)) }, text = stringResource(R.string.import_str),
onClick = { showImportSheet = true; dismiss() }, onClick = { showImportSheet = true; dismiss() },
leadingIcon = { Icon(Icons.Default.FileOpen, null) } leadingIcon = { Icon(Icons.Default.FileOpen, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("分组管理") }, text = "分组管理",
onClick = { showGroupManageSheet = true; dismiss() } onClick = { showGroupManageSheet = true; dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("帮助") }, text = "帮助",
onClick = { /*TODO*/ dismiss() } onClick = { /*TODO*/ dismiss() }
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("旧的在前") }, text = "旧的在前",
onClick = { viewModel.setSortMode("asc"); dismiss() } onClick = { viewModel.setSortMode("asc"); dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("新的在前") }, text = "新的在前",
onClick = { viewModel.setSortMode("desc"); dismiss() } onClick = { viewModel.setSortMode("desc"); dismiss() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("名称升序") }, text = "名称升序",
onClick = { onClick = {
viewModel.setSortMode("name_asc") viewModel.setSortMode("name_asc")
dismiss() dismiss()
@@ -438,7 +406,7 @@ fun ReplaceRuleScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text("名称降序") }, text = "名称降序",
onClick = { onClick = {
viewModel.setSortMode("name_desc") viewModel.setSortMode("name_desc")
dismiss() dismiss()
@@ -495,16 +463,16 @@ fun ReplaceRuleScreen(
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp)) //clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
), ),
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
DropdownMenuItem( RoundDropdownMenuItem(
text = { Text("移至顶部") }, text = "移至顶部",
onClick = { viewModel.toTop(ui.rule); dismiss() } onClick = { viewModel.toTop(ui.rule); dismiss() }
) )
DropdownMenuItem( RoundDropdownMenuItem(
text = { Text("移至底部") }, text = "移至底部",
onClick = { viewModel.toBottom(ui.rule); dismiss() } onClick = { viewModel.toBottom(ui.rule); dismiss() }
) )
DropdownMenuItem( RoundDropdownMenuItem(
text = { Text("删除") }, text = "删除",
onClick = { showDeleteRuleDialog = ui.rule; dismiss() } onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
) )
} }
@@ -52,7 +52,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
@@ -78,10 +77,11 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import io.legado.app.ui.widget.components.AppScaffold 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.AlertButton
import io.legado.app.ui.widget.components.button.TopbarNavigationButton import io.legado.app.ui.widget.components.button.TopbarNavigationButton
import io.legado.app.ui.widget.components.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 import org.koin.androidx.compose.koinViewModel
@Composable @Composable
@@ -119,7 +119,7 @@ fun ReplaceEditScreen(
.imePadding(), .imePadding(),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") }, title = if (state.id > 0) "编辑替换规则" else "新增替换规则",
navigationIcon = { navigationIcon = {
TopbarNavigationButton(onClick = onBack) TopbarNavigationButton(onClick = onBack)
}, },
@@ -143,14 +143,14 @@ fun ReplaceEditScreen(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text("复制规则") }, text = { AppText("复制规则") },
onClick = { onClick = {
showMenu = false showMenu = false
viewModel.copyRule() viewModel.copyRule()
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("粘贴规则") }, text = { AppText("粘贴规则") },
onClick = { onClick = {
showMenu = false showMenu = false
viewModel.pasteRule(onSuccess = {}) viewModel.pasteRule(onSuccess = {})
@@ -167,7 +167,7 @@ fun ReplaceEditScreen(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Above TooltipAnchorPosition.Above
), ),
tooltip = { PlainTooltip { Text("添加") } }, tooltip = { PlainTooltip { AppText("添加") } },
state = rememberTooltipState(), state = rememberTooltipState(),
) { ) {
FloatingActionButton( FloatingActionButton(
@@ -214,7 +214,7 @@ fun ReplaceEditScreen(
OutlinedTextField( OutlinedTextField(
value = state.name, value = state.name,
onValueChange = viewModel::onNameChange, onValueChange = viewModel::onNameChange,
label = { Text("规则名称") }, label = { AppText("规则名称") },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.onFocusChanged { .onFocusChanged {
@@ -234,8 +234,8 @@ fun ReplaceEditScreen(
OutlinedTextField( OutlinedTextField(
value = state.pattern, value = state.pattern,
onValueChange = viewModel::onPatternChange, onValueChange = viewModel::onPatternChange,
label = { Text("匹配规则") }, label = { AppText("匹配规则") },
placeholder = { Text("输入正则表达式或关键字") }, placeholder = { AppText("输入正则表达式或关键字") },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.onFocusChanged { .onFocusChanged {
@@ -247,8 +247,8 @@ fun ReplaceEditScreen(
OutlinedTextField( OutlinedTextField(
value = state.replacement, value = state.replacement,
onValueChange = viewModel::onReplacementChange, onValueChange = viewModel::onReplacementChange,
label = { Text("替换为") }, label = { AppText("替换为") },
placeholder = { Text("输入替换内容或捕获组") }, placeholder = { AppText("输入替换内容或捕获组") },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.onFocusChanged { .onFocusChanged {
@@ -265,7 +265,7 @@ fun ReplaceEditScreen(
FilterChip( FilterChip(
selected = state.scopeTitle, selected = state.scopeTitle,
onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) }, onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
label = { Text("标题") }, label = { AppText("标题") },
leadingIcon = if (state.scopeTitle) { leadingIcon = if (state.scopeTitle) {
{ {
Icon( Icon(
@@ -282,7 +282,7 @@ fun ReplaceEditScreen(
FilterChip( FilterChip(
selected = state.scopeContent, selected = state.scopeContent,
onClick = { viewModel.onScopeContentChange(!state.scopeContent) }, onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
label = { Text("内容") }, label = { AppText("内容") },
leadingIcon = if (state.scopeContent) { leadingIcon = if (state.scopeContent) {
{ {
Icon( Icon(
@@ -299,7 +299,7 @@ fun ReplaceEditScreen(
FilterChip( FilterChip(
selected = state.isRegex, selected = state.isRegex,
onClick = { viewModel.onRegexChange(!state.isRegex) }, onClick = { viewModel.onRegexChange(!state.isRegex) },
label = { Text("使用正则") }, label = { AppText("使用正则") },
leadingIcon = if (state.isRegex) { leadingIcon = if (state.isRegex) {
{ {
Icon( Icon(
@@ -316,8 +316,8 @@ fun ReplaceEditScreen(
OutlinedTextField( OutlinedTextField(
value = state.scope, value = state.scope,
onValueChange = viewModel::onScopeChange, onValueChange = viewModel::onScopeChange,
label = { Text("特定范围") }, label = { AppText("特定范围") },
placeholder = { Text("指定规则适用的范围") }, placeholder = { AppText("指定规则适用的范围") },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.onFocusChanged { .onFocusChanged {
@@ -329,8 +329,8 @@ fun ReplaceEditScreen(
OutlinedTextField( OutlinedTextField(
value = state.excludeScope, value = state.excludeScope,
onValueChange = viewModel::onExcludeScopeChange, onValueChange = viewModel::onExcludeScopeChange,
label = { Text("排除范围") }, label = { AppText("排除范围") },
placeholder = { Text("指定规则不适用的范围") }, placeholder = { AppText("指定规则不适用的范围") },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.onFocusChanged { .onFocusChanged {
@@ -342,8 +342,8 @@ fun ReplaceEditScreen(
OutlinedTextField( OutlinedTextField(
value = state.timeout, value = state.timeout,
onValueChange = viewModel::onTimeoutChange, onValueChange = viewModel::onTimeoutChange,
label = { Text("超时 (ms)") }, label = { AppText("超时 (ms)") },
placeholder = { Text("3000") }, placeholder = { AppText("3000") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -383,8 +383,8 @@ fun GroupSelector(
OutlinedTextField( OutlinedTextField(
value = currentGroup, value = currentGroup,
onValueChange = onGroupChange, onValueChange = onGroupChange,
label = { Text("分组") }, label = { AppText("分组") },
placeholder = { Text("默认") }, placeholder = { AppText("默认") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier modifier = Modifier
@@ -400,7 +400,7 @@ fun GroupSelector(
) { ) {
allGroups.forEach { selectionOption -> allGroups.forEach { selectionOption ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(selectionOption) }, text = { AppText(selectionOption) },
onClick = { onClick = {
onGroupChange(selectionOption) onGroupChange(selectionOption)
expanded = false expanded = false
@@ -426,9 +426,9 @@ fun ManageGroupDialog(
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
title = { Text("分组管理") }, title = { AppText("分组管理") },
text = { text = {
if (groups.isEmpty()) Text("暂无其他分组") if (groups.isEmpty()) AppText("暂无其他分组")
else Column( else Column(
modifier = Modifier.verticalScroll(rememberScrollState()), modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -448,7 +448,7 @@ fun ManageGroupDialog(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Checkbox(checked = isSelected, onCheckedChange = null) 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 = "删除选中" text = "删除选中"
) )
}, },
dismissButton = { TextButton(onClick = onDismiss) { Text("关闭") } } dismissButton = { TextButton(onClick = onDismiss) { AppText("关闭") } }
) )
} }
@@ -482,7 +482,7 @@ fun QuickInputBar(
symbols.forEach { symbol -> symbols.forEach { symbol ->
AssistChip( AssistChip(
onClick = { onInsert(symbol) }, onClick = { onInsert(symbol) },
label = { Text(symbol) }, label = { AppText(symbol) },
modifier = Modifier.padding(end = 8.dp) 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -19,7 +17,6 @@ import androidx.compose.material.icons.filled.Group
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -129,11 +126,10 @@ fun RssFavoritesScreen(
topBarActions = {}, topBarActions = {},
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.all),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp)) Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
Text(stringResource(R.string.all))
} }
}, },
onClick = { onClick = {
@@ -143,15 +139,14 @@ fun RssFavoritesScreen(
) )
groups.forEach { group -> groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = group,
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon( Icon(
Icons.AutoMirrored.Outlined.Label, Icons.AutoMirrored.Outlined.Label,
null, null,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
) )
Spacer(Modifier.width(12.dp))
Text(group)
} }
}, },
onClick = { onClick = {
@@ -162,11 +157,10 @@ fun RssFavoritesScreen(
} }
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.delete_select_group),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp)) Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
Text(stringResource(R.string.delete_select_group))
} }
}, },
onClick = { onClick = {
@@ -175,11 +169,10 @@ fun RssFavoritesScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.all),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp)) Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(12.dp))
Text(stringResource(R.string.all))
} }
}, },
onClick = { onClick = {
@@ -248,14 +241,14 @@ fun RssFavoritesScreen(
}, },
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.change_group)) }, text = stringResource(R.string.change_group),
onClick = { onClick = {
showSetGroupDialog = rssStar showSetGroupDialog = rssStar
dismiss() dismiss()
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.delete)) }, text = stringResource(R.string.delete),
onClick = { onClick = {
viewModel.deleteStar(rssStar) viewModel.deleteStar(rssStar)
dismiss() dismiss()
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -17,11 +16,9 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect 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.dialog.TextListInputDialog
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold 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 org.koin.androidx.compose.koinViewModel
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -175,46 +172,41 @@ fun RssSourceScreen(
) )
} }
if (showGroupManageSheet) { GroupManageBottomSheet(
GroupManageBottomSheet( show = showGroupManageSheet,
groups = groups, groups = groups,
onDismissRequest = { showGroupManageSheet = false }, onDismissRequest = { showGroupManageSheet = false },
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) }, onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
onDeleteGroup = { viewModel.delGroup(it) } onDeleteGroup = { viewModel.delGroup(it) }
) )
}
if (showFilePickerSheet) {
FilePickerSheet(
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 -> FilePickerSheet(
BatchImportDialog( show = showFilePickerSheet,
title = stringResource(R.string.import_rss_source), onDismissRequest = { showFilePickerSheet = false },
importState = state, onSelectSysDir = {
onDismissRequest = { viewModel.cancelImport() }, showFilePickerSheet = false
onToggleItem = { viewModel.toggleImportSelection(it) }, exportDoc.launch("exportRssSource.json")
onToggleAll = { viewModel.toggleImportAll(it) }, },
onConfirm = { viewModel.saveImportedRules() }, onUpload = {
itemContent = { source, _ -> showFilePickerSheet = false
Column { viewModel.uploadSelectedRules(selectedIds, rules)
Text(source.sourceName, style = MaterialTheme.typography.titleMedium) },
Text(source.sourceUrl, style = MaterialTheme.typography.bodySmall, maxLines = 1) 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) { LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) {
@@ -225,16 +217,16 @@ fun RssSourceScreen(
showDeleteRuleDialog?.let { source -> showDeleteRuleDialog?.let { source ->
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteRuleDialog = null }, onDismissRequest = { showDeleteRuleDialog = null },
title = { Text(stringResource(R.string.delete)) }, title = { AppText(stringResource(R.string.delete)) },
text = { Text(stringResource(R.string.del_msg)) }, text = { AppText(stringResource(R.string.del_msg)) },
confirmButton = { confirmButton = {
OutlinedButton(onClick = { OutlinedButton(onClick = {
viewModel.del(source); showDeleteRuleDialog = null viewModel.del(source); showDeleteRuleDialog = null
}) { Text(stringResource(R.string.ok)) } }) { AppText(stringResource(R.string.ok)) }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = { showDeleteRuleDialog = null }) { TextButton(onClick = { showDeleteRuleDialog = null }) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
} }
) )
@@ -287,11 +279,11 @@ fun RssSourceScreen(
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
onClick = { showGroupManageSheet = true }, onClick = { showGroupManageSheet = true },
text = { Text("分组管理") }, text = "分组管理",
) )
Box { Box {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_rss_source)) }, text = stringResource(R.string.import_rss_source),
onClick = { showImportMenu = true } onClick = { showImportMenu = true }
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -299,7 +291,7 @@ fun RssSourceScreen(
onDismissRequest = { showImportMenu = false } onDismissRequest = { showImportMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_on_line)) }, text = stringResource(R.string.import_on_line),
onClick = { onClick = {
showImportMenu = false showImportMenu = false
dismiss() dismiss()
@@ -307,7 +299,7 @@ fun RssSourceScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_local)) }, text = stringResource(R.string.import_local),
onClick = { onClick = {
showImportMenu = false showImportMenu = false
dismiss() dismiss()
@@ -315,7 +307,7 @@ fun RssSourceScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.import_default_rule)) }, text = stringResource(R.string.import_default_rule),
onClick = { onClick = {
showImportMenu = false showImportMenu = false
dismiss() dismiss()
@@ -326,29 +318,29 @@ fun RssSourceScreen(
} }
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.all)) }, text = stringResource(R.string.all),
onClick = { dismiss(); viewModel.setGroupFilter(null) } onClick = { dismiss(); viewModel.setGroupFilter(null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.enabled)) }, text = stringResource(R.string.enabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.disabled)) }, text = stringResource(R.string.disabled),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.need_login)) }, text = stringResource(R.string.need_login),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.no_group)) }, text = stringResource(R.string.no_group),
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) } onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) }
) )
PillDivider() PillDivider()
groups.forEach { group -> groups.forEach { group ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(group) }, text = group,
onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$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.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
@@ -54,12 +52,14 @@ import io.legado.app.data.entities.RuleSubType
import io.legado.app.ui.association.ImportBookSourceDialog import io.legado.app.ui.association.ImportBookSourceDialog
import io.legado.app.ui.association.ImportReplaceRuleDialog import io.legado.app.ui.association.ImportReplaceRuleDialog
import io.legado.app.ui.association.ImportRssSourceDialog 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.EmptyMessageView
import io.legado.app.ui.widget.components.card.SelectionItemCard 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.CheckboxGroupContainer
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.rules.RuleListScaffold import io.legado.app.ui.widget.components.rules.RuleListScaffold
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.showDialogFragment import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -89,11 +89,12 @@ fun RuleSubScreen(
topBarActions = {}, topBarActions = {},
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { text = stringResource(R.string.sort),
leadingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp)) Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp))
Spacer(Modifier.size(12.dp)) Spacer(Modifier.size(12.dp))
Text(stringResource(R.string.sort)) AppText(stringResource(R.string.sort))
} }
}, },
onClick = { onClick = {
@@ -106,7 +107,7 @@ fun RuleSubScreen(
TooltipBox( TooltipBox(
positionProvider = positionProvider =
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text("Localized description") } }, tooltip = { PlainTooltip { AppText("Localized description") } },
state = rememberTooltipState(), state = rememberTooltipState(),
) { ) {
FloatingActionButton( FloatingActionButton(
@@ -185,7 +186,7 @@ fun RuleSubScreen(
}, },
dropdownContent = { dismiss -> dropdownContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = { Text(stringResource(R.string.delete)) }, text = stringResource(R.string.delete),
onClick = { onClick = {
viewModel.delete(ruleSub) viewModel.delete(ruleSub)
dismiss() dismiss()
@@ -226,24 +227,24 @@ fun RuleSubEditDialog(
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.rule_subscription)) }, title = { AppText(stringResource(R.string.rule_subscription)) },
text = { text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { Text(stringResource(R.string.name)) }, label = { AppText(stringResource(R.string.name)) },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
OutlinedTextField( OutlinedTextField(
value = url, value = url,
onValueChange = { url = it }, onValueChange = { url = it },
label = { Text("URL") }, label = { AppText("URL") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Text( AppText(
text = "订阅类型", text = "订阅类型",
style = MaterialTheme.typography.titleSmall, style = LegadoTheme.typography.titleSmall,
modifier = Modifier.padding(top = 8.dp) modifier = Modifier.padding(top = 8.dp)
) )
CheckboxGroupContainer(columns = 2) { CheckboxGroupContainer(columns = 2) {
@@ -265,12 +266,12 @@ fun RuleSubEditDialog(
TextButton(onClick = { TextButton(onClick = {
onConfirm(ruleSub.copy(name = name, url = url, type = type)) onConfirm(ruleSub.copy(name = name, url = url, type = type))
}) { }) {
Text(stringResource(R.string.ok)) AppText(stringResource(R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = onDismiss) { 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.Shapes
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.theme.ThemeController
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -20,29 +25,274 @@ fun AppTheme(
val context = LocalContext.current val context = LocalContext.current
val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme) val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme)
val isPureBlack = ThemeConfig.isPureBlack val isPureBlack = ThemeConfig.isPureBlack
val paletteStyle = ThemeConfig.paletteStyle val paletteStyleStr = ThemeConfig.paletteStyle
val materialVersion = ThemeConfig.materialVersion 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 = val colorScheme =
remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle, materialVersion) { remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyleStr, materialVersion) {
ThemeManager.getColorScheme( ThemeManager.getColorScheme(
context = context, context = context,
mode = appThemeMode, mode = appThemeMode,
darkTheme = darkTheme, darkTheme = darkTheme,
isAmoled = isPureBlack, isAmoled = isPureBlack,
paletteStyle = paletteStyle, paletteStyle = paletteStyleStr,
materialVersion = materialVersion materialVersion = materialVersion
) )
} }
MaterialExpressiveTheme( val themeColors = remember(
colorScheme = colorScheme, colorScheme,
typography = Typography(), darkTheme,
motionScheme = MotionScheme.expressive(), seedColor,
shapes = Shapes() paletteStyle,
colorSchemeMode,
composeEngine
) { ) {
AppBackground(darkTheme = darkTheme) { LegadoThemeMode(
content() 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.hazeEffect
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi 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.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.hazeStyle.HazeLegado
/** /**
* 自动感知全局配置的 HazeSource * 自动感知全局配置的 HazeSource
@@ -31,9 +31,9 @@ fun Modifier.responsiveHazeEffect(
if (!enableBlur) return this if (!enableBlur) return this
val style = if (enableProgressiveBlur) { val style = if (enableProgressiveBlur) {
HazeMaterials.ultraThin() HazeLegado.ultraThin()
} else { } else {
HazeMaterials.regular() HazeLegado.regular()
} }
return this.hazeEffect( return this.hazeEffect(
@@ -61,6 +61,6 @@ fun Modifier.regularHazeEffect(state: HazeState): Modifier {
return this.hazeEffect( return this.hazeEffect(
state = state, 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 -> { AppThemeMode.CUSTOM -> {
CustomColorScheme(context.primaryColor, style) val colorSpec = ThemeResolver.resolveColorSpecFromMaterialVersion(materialVersion)
CustomColorScheme(context.primaryColor, style, colorSpec)
.getColorScheme(darkTheme) .getColorScheme(darkTheme)
} }
@@ -93,4 +94,4 @@ object ThemeManager {
return scheme return scheme
} }
} }
@@ -4,6 +4,7 @@ import com.materialkolor.Contrast
import com.materialkolor.PaletteStyle import com.materialkolor.PaletteStyle
import com.materialkolor.dynamiccolor.ColorSpec import com.materialkolor.dynamiccolor.ColorSpec
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
object ThemeResolver { 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 { fun resolveColorSpecVersion(colorSpec: ThemeColorSpec): ColorSpec.SpecVersion {
return when (colorSpec) { return when (colorSpec) {
ThemeColorSpec.SPEC_2025 -> ColorSpec.SpecVersion.SPEC_2025 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.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment 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.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun CollapsibleHeader( fun CollapsibleHeader(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showIcon: Boolean = true,
isCollapsed: Boolean, isCollapsed: Boolean,
onToggle: () -> Unit, onToggle: () -> Unit,
title: String, title: String,
subtitle: String? = null, subtitle: String? = null,
titleContent: (@Composable ColumnScope.() -> Unit)? = null titleContent: (@Composable ColumnScope.() -> Unit)? = null
) { ) {
Card( GlassCard(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 16.dp, top = 4.dp, end = 16.dp), .padding(start = 16.dp, top = 4.dp, end = 16.dp),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors( containerColor = LegadoTheme.colorScheme.surfaceContainer,
containerColor = MaterialTheme.colorScheme.surfaceContainer
),
onClick = onToggle onClick = onToggle
) { ) {
Row( Row(
@@ -54,20 +53,20 @@ fun CollapsibleHeader(
if (titleContent != null) { if (titleContent != null) {
titleContent() titleContent()
} else { } else {
Text( AppText(
text = title, text = title,
style = MaterialTheme.typography.bodySmallEmphasized.copy( style = LegadoTheme.typography.bodySmallEmphasized.copy(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary color = LegadoTheme.colorScheme.primary
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
subtitle?.let { subtitle?.let {
Text( AppText(
text = it, text = it,
style = MaterialTheme.typography.labelSmallEmphasized.copy( style = LegadoTheme.typography.labelSmallEmphasized.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -81,12 +80,14 @@ fun CollapsibleHeader(
label = "arrowRotation" label = "arrowRotation"
) )
Icon( if (showIcon) {
imageVector = Icons.Default.ExpandMore, Icon(
contentDescription = if (isCollapsed) "展开" else "折叠", imageVector = Icons.Default.ExpandMore,
modifier = Modifier.rotate(rotation), contentDescription = if (isCollapsed) "展开" else "折叠",
tint = MaterialTheme.colorScheme.primary 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.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition import androidx.compose.material3.FabPosition
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import io.legado.app.ui.config.themeConfig.ThemeConfig 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 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AppScaffold( fun AppScaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: (@Composable () -> Unit)? = null, topBar: @Composable (HazeState) -> Unit = {},
subtitle: (@Composable () -> Unit)? = null,
scrollBehavior: TopAppBarScrollBehavior? = null,
navigationIcon: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {},
colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {}, bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {}, snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
contentColor: Color = contentColorFor(MaterialTheme.colorScheme.background), contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit content: @Composable (PaddingValues) -> Unit
) { ) {
val isDark = isSystemInDarkTheme() val isDark = isSystemInDarkTheme()
val hasImageBg = ThemeConfig.hasImageBg(isDark) val hasImageBg = ThemeConfig.hasImageBg(isDark)
val hazeState = remember { HazeState() } val hazeState = remember { HazeState() }
val composeEngine = LegadoTheme.composeEngine
val containerColor = if (hasImageBg) { val containerColor = if (hasImageBg) {
Color.Transparent Color.Transparent
} else { } else {
MaterialTheme.colorScheme.background LegadoTheme.colorScheme.background
} }
val finalTopBar = @Composable { val miuixContainerColor = if (hasImageBg) {
Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) { Color.Transparent
if (title != null) { } else {
GlassMediumFlexibleTopAppBar( MiuixTheme.colorScheme.surface
title = title, }
subtitle = subtitle,
scrollBehavior = scrollBehavior, CompositionLocalProvider(
navigationIcon = navigationIcon, LocalHazeState provides if (ThemeConfig.enableBlur) hazeState else null
actions = actions, ) {
colors = colors when {
) ThemeResolver.isMiuixEngine(composeEngine) -> {
} else { val miuixFabPosition = when (floatingActionButtonPosition) {
topBar() 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.foundation.layout.wrapContentSize
import androidx.compose.material3.ContainedLoadingIndicator import androidx.compose.material3.ContainedLoadingIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -24,6 +23,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -70,7 +71,7 @@ fun EmptyMessageView(
AnimatedTextLine( AnimatedTextLine(
text = message, text = message,
style = MaterialTheme.typography.labelLarge, style = LegadoTheme.typography.labelLarge,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
maxLines = 2, maxLines = 2,
softWrap = true, 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 package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState
@@ -18,7 +16,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -26,45 +23,36 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.button.SmallTextButton import io.legado.app.ui.widget.components.button.SmallTextButton
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.SettingItem import io.legado.app.ui.widget.components.settingItem.SettingItem
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupManageBottomSheet( fun GroupManageBottomSheet(
show: Boolean,
groups: List<String>, groups: List<String>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit, onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit,
onDeleteGroup: (group: String) -> Unit onDeleteGroup: (group: String) -> Unit
) { ) {
GlassModalBottomSheet( AppModalBottomSheet(
onDismissRequest = onDismissRequest show = show,
onDismissRequest = onDismissRequest,
title = stringResource(R.string.group_manage),
) { ) {
Column( LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
modifier = Modifier items(groups, key = { it }) { group ->
.padding(horizontal = 16.dp) GroupItem(
.padding(bottom = 16.dp), group = group,
horizontalAlignment = Alignment.CenterHorizontally, onUpdateGroup = onUpdateGroup,
) { onDeleteGroup = onDeleteGroup
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
)
}
} }
} }
} }
@@ -115,7 +103,7 @@ private fun GroupItem(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = 48.dp), .heightIn(min = 48.dp),
label = { Text(stringResource(R.string.edit)) }, label = { AppText(stringResource(R.string.edit)) },
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = 4.dp, top = 4.dp,
bottom = 4.dp, bottom = 4.dp,
@@ -10,6 +10,40 @@ import androidx.compose.material3.SwitchDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import 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 @Composable
fun IconSwitch( fun IconSwitch(
@@ -1,33 +1,14 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.VerticalAlignTop
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -38,13 +19,15 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -53,10 +36,10 @@ fun SearchBarSection(
onQueryChange: (String) -> Unit, onQueryChange: (String) -> Unit,
placeholder: String = "搜索...", placeholder: String = "搜索...",
leadingIcon: @Composable (() -> Unit)? = { leadingIcon: @Composable (() -> Unit)? = {
SmallIconButton( AppIcon(
icon = Icons.Default.Search, imageVector = AppIcons.Search,
contentDescription = null, contentDescription = null
onClick = {}) )
}, },
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow, backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow,
scrollState: LazyListState? = null, scrollState: LazyListState? = null,
@@ -65,7 +48,6 @@ fun SearchBarSection(
dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null
) { ) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
val textFieldState = rememberTextFieldState(initialText = query) val textFieldState = rememberTextFieldState(initialText = query)
LaunchedEffect(query) { LaunchedEffect(query) {
@@ -91,79 +73,19 @@ fun SearchBarSection(
} }
} }
Surface( val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
modifier = Modifier
.fillMaxWidth() val searchTextField = @Composable {
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp), AppTextField(
shape = RoundedCornerShape(32.dp),
color = backgroundColor
) {
TextField(
state = textFieldState, state = textFieldState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = 48.dp) .then(
.padding(horizontal = 4.dp), if (isMiuix) Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
placeholder = { Text(placeholder) }, else Modifier
),
placeholder = { AppText(placeholder) },
leadingIcon = leadingIcon, leadingIcon = leadingIcon,
trailingIcon = {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(end = 4.dp)
.animateContentSize()
) {
AnimatedVisibility(
visible = textFieldState.text.isNotEmpty(),
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
SmallIconButton(
onClick = { textFieldState.clearText() },
icon = Icons.Default.Clear,
contentDescription = "清空输入"
)
}
AnimatedVisibility(
visible = showScrollToTop,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
SmallIconButton(
onClick = {
scope.launch {
scrollState?.animateScrollToItem(0)
}
},
icon = Icons.Default.VerticalAlignTop,
contentDescription = "回到顶部"
)
}
if (trailingIcon != null) {
Box {
IconButton(
onClick = {
if (dropdownMenu != null) showMenu = true
}
) {
trailingIcon()
}
if (dropdownMenu != null) {
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
dropdownMenu { showMenu = false }
}
}
}
}
}
},
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent,
@@ -171,13 +93,21 @@ fun SearchBarSection(
disabledContainerColor = Color.Transparent, disabledContainerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
),
contentPadding = PaddingValues(
top = 4.dp,
bottom = 4.dp,
start = 12.dp,
end = 12.dp
) )
) )
} }
if (isMiuix) {
searchTextField()
} else {
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
shape = RoundedCornerShape(32.dp),
color = backgroundColor
) {
searchTextField()
}
}
} }
@@ -15,7 +15,6 @@ import androidx.compose.material3.HorizontalFloatingToolbar
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TooltipDefaults
@@ -27,6 +26,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.widget.components.text.AppText
data class SelectionActions( data class SelectionActions(
val primaryAction: ActionItem, val primaryAction: ActionItem,
@@ -84,7 +84,7 @@ fun SelectionBottomBar(
) { ) {
secondaryActions.forEach { action -> secondaryActions.forEach { action ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(action.text) }, text = { AppText(action.text) },
leadingIcon = action.icon, leadingIcon = action.icon,
onClick = { onClick = {
action.onClick() action.onClick()
@@ -101,7 +101,7 @@ fun SelectionBottomBar(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Above TooltipAnchorPosition.Above
), ),
tooltip = { PlainTooltip { Text(primaryAction.text) } }, tooltip = { PlainTooltip { AppText(primaryAction.text) } },
state = rememberTooltipState(), state = rememberTooltipState(),
) { ) {
FilledIconButton( FilledIconButton(
@@ -7,14 +7,14 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.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.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.draw.clip
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.widget.components.title.AdaptiveTitle
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
/** /**
* Settings Group Container by https://github.com/wxxsfxyzm/InstallerX-Revived * Settings Group Container by https://github.com/wxxsfxyzm/InstallerX-Revived
@@ -29,24 +29,33 @@ fun SplicedColumnGroup(
title: String = "", title: String = "",
content: @Composable ColumnScope.() -> Unit, 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()) { if (title.isNotEmpty()) {
Text( AdaptiveTitle(
text = title, text = title,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 16.dp, bottom = 8.dp) modifier = Modifier.padding(start = 16.dp, bottom = 8.dp)
) )
} }
Card( if (ThemeResolver.isMiuixEngine(composeEngine)) {
shape = RoundedCornerShape(16.dp), MiuixCard {
colors = CardDefaults.cardColors(containerColor = Color.Transparent), Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.animateContentSize() .animateContentSize()
) { .clip(RoundedCornerShape(16.dp))
) {
content()
}
}
} else {
Column( Column(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.clip(RoundedCornerShape(16.dp)),
verticalArrangement = Arrangement.spacedBy(2.dp) verticalArrangement = Arrangement.spacedBy(2.dp)
) { ) {
content() 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -26,11 +22,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookmarkEditSheet( fun BookmarkEditSheet(
show: Boolean,
bookmark: Bookmark, bookmark: Bookmark,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSave: (Bookmark) -> Unit, onSave: (Bookmark) -> Unit,
@@ -41,25 +41,20 @@ fun BookmarkEditSheet(
var bookText by remember { mutableStateOf(bookmark.bookText) } var bookText by remember { mutableStateOf(bookmark.bookText) }
var content by remember { mutableStateOf(bookmark.content) } var content by remember { mutableStateOf(bookmark.content) }
GlassModalBottomSheet( AppModalBottomSheet(
onDismissRequest = onDismiss show = show,
onDismissRequest = onDismiss,
title = bookmark.chapterName,
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp)
.navigationBarsPadding() .navigationBarsPadding()
) { ) {
Text(
text = bookmark.chapterName,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField( OutlinedTextField(
value = bookText, value = bookText,
onValueChange = { bookText = it }, onValueChange = { bookText = it },
label = { Text("原文") }, label = { AppText("原文") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
maxLines = 10 maxLines = 10
) )
@@ -69,7 +64,7 @@ fun BookmarkEditSheet(
OutlinedTextField( OutlinedTextField(
value = content, value = content,
onValueChange = { content = it }, onValueChange = { content = it },
label = { Text("摘要/笔记") }, label = { AppText("摘要/笔记") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
maxLines = 5 maxLines = 5
) )
@@ -81,15 +76,13 @@ fun BookmarkEditSheet(
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
OutlinedButton( SecondaryButton(
onClick = { showDeleteConfirmDialog = true }, onClick = { showDeleteConfirmDialog = true },
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error), modifier = Modifier.weight(1f),
modifier = Modifier.weight(1f) text = "删除"
) { )
Text("删除")
}
Button( PrimaryButton(
onClick = { onClick = {
val newBookmark = bookmark.apply { val newBookmark = bookmark.apply {
this.bookText = bookText this.bookText = bookText
@@ -97,10 +90,9 @@ fun BookmarkEditSheet(
} }
onSave(newBookmark) onSave(newBookmark)
}, },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f),
) { text = "保存"
Text("保存") )
}
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
@@ -109,8 +101,8 @@ fun BookmarkEditSheet(
if (showDeleteConfirmDialog) { if (showDeleteConfirmDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showDeleteConfirmDialog = false }, onDismissRequest = { showDeleteConfirmDialog = false },
title = { Text("确认删除") }, title = { AppText("确认删除") },
text = { Text("你确定要删除这条书签吗?") }, text = { AppText("你确定要删除这条书签吗?") },
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
@@ -119,14 +111,14 @@ fun BookmarkEditSheet(
}, },
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error) colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
) { ) {
Text("删除") AppText("删除")
} }
}, },
dismissButton = { dismissButton = {
TextButton( TextButton(
onClick = { showDeleteConfirmDialog = false } 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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -30,7 +30,7 @@ fun BookmarkItem(
onLongClick: () -> Unit onLongClick: () -> Unit
) { ) {
val backgroundColor by animateColorAsState( val backgroundColor by animateColorAsState(
targetValue = if (isDur) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f) targetValue = if (isDur) LegadoTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
else Color.Transparent, else Color.Transparent,
label = "BgColor" label = "BgColor"
) )
@@ -49,29 +49,29 @@ fun BookmarkItem(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
) { ) {
Text( AppText(
text = bookmark.chapterName, text = bookmark.chapterName,
style = MaterialTheme.typography.titleSmallEmphasized, style = LegadoTheme.typography.titleSmallEmphasized,
color = if (isDur) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.secondary
) )
if (bookmark.bookText.isNotEmpty()) { if (bookmark.bookText.isNotEmpty()) {
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( AppText(
text = bookmark.bookText, text = bookmark.bookText,
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurfaceVariant color = LegadoTheme.colorScheme.onSurfaceVariant
) )
} }
if (bookmark.content.isNotEmpty()) { if (bookmark.content.isNotEmpty()) {
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
Text( AppText(
text = bookmark.content, text = bookmark.content,
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary 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.ButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import io.legado.app.ui.widget.components.text.AppText
@Composable @Composable
fun AlertButton( fun AlertButton(
@@ -24,6 +24,6 @@ fun AlertButton(
containerColor = Color.Transparent, 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.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton import androidx.compose.material3.ToggleButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -21,6 +19,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -73,9 +73,9 @@ fun AnimatedActionButton(
AnimatedVisibility( AnimatedVisibility(
visible = showText visible = showText
) { ) {
Text( AppText(
text = if (lastCheckedState) activeText else inactiveText, text = if (lastCheckedState) activeText else inactiveText,
style = MaterialTheme.typography.labelMedium, style = LegadoTheme.typography.labelMedium,
modifier = Modifier.padding(start = 8.dp), modifier = Modifier.padding(start = 8.dp),
maxLines = 1, maxLines = 1,
softWrap = false softWrap = false
@@ -8,11 +8,11 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.material3.ContainedLoadingIndicator import androidx.compose.material3.ContainedLoadingIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.legado.app.ui.widget.components.text.AppText
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -38,7 +38,7 @@ fun AnimatedTextButton(
if (loading) { if (loading) {
ContainedLoadingIndicator() ContainedLoadingIndicator()
} else { } 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 package io.legado.app.ui.widget.components.button
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -15,11 +17,9 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconToggleButtonShapes import androidx.compose.material3.IconToggleButtonShapes
import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.OutlinedIconToggleButton import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.OutlinedToggleButton import androidx.compose.material3.OutlinedToggleButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -31,7 +31,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.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 kotlinx.coroutines.delay
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -41,22 +47,33 @@ fun SmallIconButton(
icon: ImageVector, icon: ImageVector,
contentDescription: String? = null contentDescription: String? = null
) { ) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { if (ThemeResolver.isMiuixEngine(composeEngine)) {
IconButton( MiuixIconButton(
onClick = onClick, onClick = onClick
modifier = Modifier.size(
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shape = IconButtonDefaults.extraSmallRoundShape,
) { ) {
Icon( MiuixIcon(
imageVector = icon, imageVector = icon,
contentDescription = contentDescription, contentDescription = contentDescription
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
) )
} }
} 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( fun SmallOutlinedIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, icon: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) {
MiuixIconButton(
onClick = onClick,
modifier = modifier.size(32.dp),
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) {
MiuixIcon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(18.dp)
)
}
} else {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
OutlinedIconButton( OutlinedIconButton(
onClick = onClick, onClick = onClick,
@@ -87,6 +120,7 @@ fun SmallOutlinedIconButton(
) )
} }
} }
}
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -94,27 +128,42 @@ fun SmallOutlinedIconButton(
fun SmallTonalIconButton( fun SmallTonalIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, icon: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
FilledTonalIconButton(
if (isMiuix) {
MiuixIconButton(
onClick = onClick, onClick = onClick,
modifier = Modifier.size( modifier = modifier.size(32.dp),
IconButtonDefaults.extraSmallContainerSize( backgroundColor = LegadoTheme.colorScheme.surfaceContainer
IconButtonDefaults.IconButtonWidthOption.Uniform
)
),
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.filledTonalIconButtonColors()
) { ) {
Icon( MiuixIcon(
imageVector = icon, imageVector = icon,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size( modifier = Modifier.size(18.dp)
IconButtonDefaults.extraSmallIconSize
)
) )
} }
} 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, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
val defaultShape = IconButtonDefaults.extraSmallRoundShape val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
val pressedShape = IconButtonDefaults.extraSmallPressedShape
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
val toggleShapes = remember(defaultShape, checkedShape) { if (isMiuix) {
IconToggleButtonShapes(
shape = defaultShape, val containerColor by animateColorAsState(
pressedShape = pressedShape, targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
checkedShape = checkedShape animationSpec = tween(150),
label = "MiuixToggleContainerColor"
) )
}
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { val iconTint by animateColorAsState(
OutlinedIconToggleButton( targetValue = if (checked) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant,
checked = checked, animationSpec = tween(150),
onCheckedChange = onCheckedChange, label = "MiuixToggleIconTint"
modifier = modifier.size( )
IconButtonDefaults.extraSmallContainerSize(
IconButtonDefaults.IconButtonWidthOption.Uniform MiuixIconButton(
) onClick = { onCheckedChange(!checked) },
), modifier = modifier
shapes = toggleShapes .size(32.dp),
backgroundColor = containerColor
) { ) {
Icon( MiuixIcon(
imageVector = icon, imageVector = icon,
contentDescription = contentDescription, 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( AnimatedVisibility(
visible = showText visible = showText
) { ) {
Text( AppText(
text = if (lastCheckedState) activeText else inactiveText, text = if (lastCheckedState) activeText else inactiveText,
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
modifier = Modifier.padding(start = 6.dp), modifier = Modifier.padding(start = 6.dp),
maxLines = 1, maxLines = 1,
softWrap = false softWrap = false
@@ -6,12 +6,18 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LegadoTheme.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 @Composable
fun SmallTextButton( fun SmallTextButton(
@@ -20,21 +26,33 @@ fun SmallTextButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: () -> Unit onClick: () -> Unit
) { ) {
TextButton( if (ThemeResolver.isMiuixEngine(composeEngine)) {
onClick = onClick, MiuixButton(
modifier = modifier, onClick = onClick,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp), modifier = modifier,
shape = MaterialTheme.shapes.small colors = MiuixButtonDefaults.buttonColors()
) { ) {
Icon( MiuixText(
imageVector = icon, text = text
contentDescription = null, )
modifier = Modifier.size(16.dp) }
) } else {
Spacer(Modifier.width(4.dp)) TextButton(
Text( onClick = onClick,
text = text, modifier = modifier,
style = MaterialTheme.typography.labelMedium 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.IconButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton import androidx.compose.material3.ToggleButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect 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.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.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 import kotlinx.coroutines.delay
enum class TopBarButtonVariant { enum class TopBarButtonVariant {
@@ -105,17 +108,26 @@ fun TopBarButton(
fun TopbarNavigationButton( fun TopbarNavigationButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack, imageVector: ImageVector = AppIcons.Back,
contentDescription: String? = "返回", contentDescription: String? = "返回",
style: TopBarButtonVariant = TopBarButtonVariant.Filled style: TopBarButtonVariant = TopBarButtonVariant.Filled
) { ) {
TopBarButton( if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
onClick = onClick, MiuixIconButton(
imageVector = imageVector, onClick = onClick,
contentDescription = contentDescription, imageVector = imageVector,
modifier = modifier.padding(horizontal = 12.dp), contentDescription = contentDescription,
style = style modifier = modifier.padding(horizontal = 16.dp)
) )
} else {
TopBarButton(
onClick = onClick,
imageVector = imageVector,
contentDescription = contentDescription,
modifier = modifier.padding(horizontal = 12.dp),
style = style
)
}
} }
@Composable @Composable
@@ -126,21 +138,29 @@ fun TopBarActionButton(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val enableProgressive = ThemeConfig.enableProgressiveBlur val enableProgressive = ThemeConfig.enableProgressiveBlur
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
if (enableProgressive) { MiuixIconButton(
TopBarButton(
onClick = onClick, onClick = onClick,
imageVector = imageVector, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = modifier.padding(end = 12.dp), modifier = modifier.padding(end = 8.dp),
style = TopBarButtonVariant.Filled
) )
} else { } else {
IconButton(onClick = onClick) { if (enableProgressive) {
Icon( TopBarButton(
onClick = onClick,
imageVector = imageVector, 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 showText = false
} }
} }
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
ToggleButton( MiuixToggleButton(
modifier = modifier.height(36.dp), checked = checked,
contentPadding = PaddingValues(horizontal = 8.dp), onCheckedChange = onCheckedChange,
checked = checked, iconChecked = iconChecked,
onCheckedChange = { iconUnchecked = iconUnchecked
lastCheckedState = it )
onCheckedChange(it) } else {
showText = true ToggleButton(
} modifier = modifier.height(36.dp),
) { contentPadding = PaddingValues(horizontal = 8.dp),
Row( checked = checked,
verticalAlignment = Alignment.CenterVertically, onCheckedChange = {
horizontalArrangement = Arrangement.Center lastCheckedState = it
) { onCheckedChange(it)
showText = true
AnimatedContent(
targetState = checked,
label = "IconAnimation"
) { targetChecked ->
AnimatedIcon(
modifier = Modifier.size(20.dp),
imageVector = if (targetChecked) iconChecked else iconUnchecked,
contentDescription = null
)
} }
) {
AnimatedVisibility( Row(
visible = showText verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) { ) {
Text(
text = if (lastCheckedState) activeText else inactiveText, AnimatedContent(
style = MaterialTheme.typography.labelMedium, targetState = checked,
modifier = Modifier.padding(start = 8.dp), label = "IconAnimation"
maxLines = 1, ) { targetChecked ->
softWrap = false 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.foundation.layout.ColumnScope
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation import androidx.compose.material3.CardElevation
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape 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.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) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -19,43 +84,50 @@ fun GlassCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape, shape: Shape = CardDefaults.shape,
colors: CardColors? = null, cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
containerColor: Color? = null,
contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(), elevation: CardElevation = CardDefaults.cardElevation(),
border: BorderStroke? = null, border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit 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( @OptIn(ExperimentalMaterialApi::class)
containerColor = MaterialTheme.colorScheme.secondaryContainer, @Composable
contentColor = MaterialTheme.colorScheme.onSecondaryContainer 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.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -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.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex 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.button.SmallIconButton
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.text.AppText
import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.ReorderableLazyListState import sh.calvin.reorderable.ReorderableLazyListState
import top.yukonga.miuix.kmp.basic.BasicComponent
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable @Composable
fun SelectionItemCard( fun SelectionItemCard(
@@ -66,11 +71,14 @@ fun SelectionItemCard(
) { ) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
val animatedContainerColor by animateColorAsState( val animatedContainerColor by animateColorAsState(
targetValue = if (isSelected) targetValue = if (isSelected)
selectedContainerColor ?: MaterialTheme.colorScheme.secondaryContainer selectedContainerColor
?: if (composeEngine) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
else else
containerColor ?: MaterialTheme.colorScheme.surfaceContainerLow, containerColor
?: if (composeEngine) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing), animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
label = "CardColor" label = "CardColor"
) )
@@ -78,10 +86,9 @@ fun SelectionItemCard(
GlassCard( GlassCard(
onClick = onToggleSelection, onClick = onToggleSelection,
modifier = modifier modifier = modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(), .fillMaxWidth(),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
colors = CardDefaults.cardColors(containerColor = animatedContainerColor), containerColor = animatedContainerColor,
elevation = CardDefaults.cardElevation(defaultElevation = elevation) elevation = CardDefaults.cardElevation(defaultElevation = elevation)
) { ) {
Row( Row(
@@ -113,28 +120,49 @@ fun SelectionItemCard(
} }
} }
ListItem( if (composeEngine) {
modifier = Modifier.weight(1f), BasicComponent(
headlineContent = { modifier = Modifier.weight(1f)
Text( ) {
AppText(
text = title, text = title,
style = MaterialTheme.typography.titleSmall, style = LegadoTheme.typography.titleSmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
}, if (!subtitle.isNullOrBlank()) {
supportingContent = if (!subtitle.isNullOrBlank()) { AppText(
{
Text(
text = subtitle, text = subtitle,
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis 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( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -143,7 +171,7 @@ fun SelectionItemCard(
.padding(end = 8.dp) .padding(end = 8.dp)
) { ) {
onEnabledChange?.let { onEnabledChange?.let {
Switch( AdaptiveSwitch(
modifier = Modifier.scale(0.8f), modifier = Modifier.scale(0.8f),
checked = isEnabled, checked = isEnabled,
onCheckedChange = it 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.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle 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 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 @Composable
fun TextCard( fun TextCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
text: String, text: String,
icon: ImageVector? = null, icon: ImageVector? = null,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
backgroundColor: Color = colorScheme.primaryContainer, backgroundColor: Color? = null,
contentColor: Color = colorScheme.onPrimaryContainer, contentColor: Color? = null,
cornerRadius: Dp = 8.dp, cornerRadius: Dp = 8.dp,
horizontalPadding: Dp = 8.dp, horizontalPadding: Dp = 8.dp,
verticalPadding: Dp = 2.dp, verticalPadding: Dp = 2.dp,
iconSize: Dp = 14.dp, iconSize: Dp = 14.dp,
spacing: Dp = 4.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, modifier = modifier,
shape = RoundedCornerShape(cornerRadius), shape = RoundedCornerShape(cornerRadius),
colors = CardDefaults.cardColors(containerColor = backgroundColor), containerColor = finalBackgroundColor,
contentColor = finalContentColor,
onClick = onClick onClick = onClick
) { ) {
Row( Row(
@@ -61,10 +58,10 @@ fun TextCard(
) { ) {
if (icon != null) { if (icon != null) {
Icon( AppIcon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
tint = contentColor, tint = finalContentColor,
modifier = Modifier.size(iconSize) modifier = Modifier.size(iconSize)
) )
Spacer(modifier = Modifier.width(spacing)) Spacer(modifier = Modifier.width(spacing))
@@ -73,7 +70,7 @@ fun TextCard(
AnimatedTextLine( AnimatedTextLine(
text = text, text = text,
style = textStyle, 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.foundation.layout.padding
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme 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.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
@Composable @Composable
fun CheckboxItem( fun CheckboxItem(
@@ -45,9 +46,9 @@ fun CheckboxItem(
enabled = enabled, enabled = enabled,
modifier = Modifier.alpha(alpha) modifier = Modifier.alpha(alpha)
) )
Text( AppText(
text = title, text = title,
style = MaterialTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
maxLines = 1, maxLines = 1,
modifier = Modifier modifier = Modifier
.padding(start = 12.dp) .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.Icons
import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Update
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
@Composable @Composable
@@ -44,8 +44,6 @@ fun BookshelfCover(
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(2.dp), .padding(2.dp),
backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
cornerRadius = 4.dp, cornerRadius = 4.dp,
horizontalPadding = 4.dp, horizontalPadding = 4.dp,
verticalPadding = 0.dp verticalPadding = 0.dp
@@ -59,8 +57,8 @@ fun BookshelfCover(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 6.dp) .padding(horizontal = 4.dp, vertical = 6.dp)
.height(3.dp), .height(3.dp),
color = MaterialTheme.colorScheme.primary, color = LegadoTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f), trackColor = LegadoTheme.colorScheme.primary.copy(alpha = 0.2f),
strokeCap = StrokeCap.Round strokeCap = StrokeCap.Round
) )
} }
@@ -22,7 +22,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue 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.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController import com.github.skydoves.colorpicker.compose.rememberColorPickerController
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ColorPickerSheet( fun ColorPickerSheet(
show: Boolean,
initialColor: Int, initialColor: Int,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onColorSelected: (Int) -> Unit onColorSelected: (Int) -> Unit
@@ -79,16 +81,16 @@ fun ColorPickerSheet(
) )
} }
GlassModalBottomSheet(onDismissRequest = onDismissRequest) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( AppText(
text = "颜色选择", text = "颜色选择",
style = MaterialTheme.typography.titleLarge, style = LegadoTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp) modifier = Modifier.padding(bottom = 16.dp)
) )
@@ -168,9 +170,9 @@ fun ColorPickerSheet(
) )
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Text( AppText(
text = "#${Integer.toHexString(currentColor.toArgb()).uppercase()}", text = "#${Integer.toHexString(currentColor.toArgb()).uppercase()}",
style = MaterialTheme.typography.bodyLarge style = LegadoTheme.typography.bodyLarge
) )
Row( Row(
@@ -178,14 +180,14 @@ fun ColorPickerSheet(
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel)) AppText(stringResource(R.string.cancel))
} }
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Button(onClick = { Button(onClick = {
onColorSelected(currentColor.toArgb()) onColorSelected(currentColor.toArgb())
onDismissRequest() 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.foundation.lazy.items
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip import androidx.compose.material3.AssistChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -22,6 +20,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AppText
@Composable @Composable
fun TextListInputDialog( fun TextListInputDialog(
@@ -36,22 +36,22 @@ fun TextListInputDialog(
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = { Text(title) }, title = { AppText(title) },
text = { text = {
Column { Column {
OutlinedTextField( OutlinedTextField(
value = text, value = text,
onValueChange = { text = it }, onValueChange = { text = it },
label = { Text(hint) }, label = { AppText(hint) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true singleLine = true
) )
if (suggestions.isNotEmpty()) { if (suggestions.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
text = "建议:", text = "建议:",
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
modifier = Modifier.padding(bottom = 4.dp) modifier = Modifier.padding(bottom = 4.dp)
) )
LazyRow( LazyRow(
@@ -61,7 +61,7 @@ fun TextListInputDialog(
items(suggestions) { suggestion -> items(suggestions) { suggestion ->
AssistChip( AssistChip(
onClick = { text = suggestion }, onClick = { text = suggestion },
label = { Text(suggestion) } label = { AppText(suggestion) }
) )
} }
} }
@@ -72,12 +72,12 @@ fun TextListInputDialog(
TextButton( TextButton(
onClick = { onConfirm(text) } onClick = { onConfirm(text) }
) { ) {
Text(stringResource(android.R.string.ok)) AppText(stringResource(android.R.string.ok))
} }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = onDismissRequest) { 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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -14,26 +13,39 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable @Composable
fun PillDivider( fun PillDivider(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
thickness: Dp = 2.dp, thickness: Dp = 2.dp,
widthFraction: Float = 0.2f, widthFraction: Float = 0.2f,
color: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f) color: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
) { ) {
Box( val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
modifier = modifier if (isMiuix) {
.fillMaxWidth()
.padding(vertical = 4.dp),
contentAlignment = Alignment.Center
) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth(widthFraction) .fillMaxWidth()
.height(thickness) .height(4.dp)
.clip(CircleShape) .background(MiuixTheme.colorScheme.secondaryContainer)
.background(color)
) )
} 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.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -17,14 +15,16 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.text.AppText
@Composable @Composable
fun PillHeaderDivider( fun PillHeaderDivider(
title: String, title: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dotSize: Dp = 2.dp, dotSize: Dp = 2.dp,
dotColor: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f), dotColor: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
titleColor: Color = MaterialTheme.colorScheme.primary titleColor: Color = LegadoTheme.colorScheme.primary
) { ) {
Row( Row(
modifier = modifier modifier = modifier
@@ -40,9 +40,9 @@ fun PillHeaderDivider(
.background(dotColor) .background(dotColor)
) )
Text( AppText(
text = title, text = title,
style = MaterialTheme.typography.labelSmall, style = LegadoTheme.typography.labelSmall,
color = titleColor, color = titleColor,
modifier = Modifier.padding(horizontal = 12.dp), modifier = Modifier.padding(horizontal = 12.dp),
maxLines = 1 maxLines = 1
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
@@ -17,10 +16,8 @@ import androidx.compose.material.icons.filled.CloudUpload
import androidx.compose.material.icons.filled.EditNote import androidx.compose.material.icons.filled.EditNote
import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -28,7 +25,10 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.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 { enum class FilePickerSheetMode {
DIR, FILE, EXPORT DIR, FILE, EXPORT
@@ -37,6 +37,7 @@ enum class FilePickerSheetMode {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun FilePickerSheet( fun FilePickerSheet(
show: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
title: String = stringResource(R.string.select_operation), title: String = stringResource(R.string.select_operation),
onSelectSysDir: (() -> Unit)? = null, onSelectSysDir: (() -> Unit)? = null,
@@ -46,22 +47,17 @@ fun FilePickerSheet(
onUpload: (() -> Unit)? = null, onUpload: (() -> Unit)? = null,
allowExtensions: Array<String>? = null, allowExtensions: Array<String>? = null,
) { ) {
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
title = title,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
containerColor = { it.surface } containerColor = LegadoTheme.colorScheme.surface
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth(),
.padding(horizontal = 16.dp, vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 20.dp)
)
FlowRow( FlowRow(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
@@ -126,7 +122,7 @@ private fun RowScope.FilePickerOptionCard(
.weight(1f) .weight(1f)
.height(100.dp), .height(100.dp),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceContainerLow, color = LegadoTheme.colorScheme.surfaceContainerLow,
tonalElevation = 2.dp tonalElevation = 2.dp
) { ) {
Column( Column(
@@ -134,16 +130,16 @@ private fun RowScope.FilePickerOptionCard(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( AppIcon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp), modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary tint = LegadoTheme.colorScheme.primary
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AppText(
text = text, text = text,
style = MaterialTheme.typography.labelMedium, style = LegadoTheme.typography.labelMedium,
maxLines = 1 maxLines = 1
) )
} }
@@ -25,7 +25,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.OutlinedIconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -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.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.AnimatedActionButton import io.legado.app.ui.widget.components.button.AnimatedActionButton
import io.legado.app.ui.widget.components.text.AppText
import java.time.LocalDate import java.time.LocalDate
/** /**
@@ -60,7 +61,7 @@ fun HeatmapCalendarTopBar(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text("时间线", style = MaterialTheme.typography.titleMedium) AppText("时间线", style = LegadoTheme.typography.titleMedium)
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
AnimatedActionButton( AnimatedActionButton(
@@ -107,7 +108,7 @@ fun WeekdayLabelsColumn(
labels.forEachIndexed { index, label -> labels.forEachIndexed { index, label ->
if (index % 2 == 0) { if (index % 2 == 0) {
Text( AppText(
text = label, text = label,
fontSize = 10.sp, fontSize = 10.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -170,7 +171,7 @@ fun NoEarlierDataIndicator(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
"没有更早数据".forEach { char -> "没有更早数据".forEach { char ->
Text( AppText(
text = char.toString(), text = char.toString(),
fontSize = 9.sp, fontSize = 9.sp,
lineHeight = 12.sp, lineHeight = 12.sp,
@@ -252,7 +253,7 @@ fun HeatmapWeekColumn(
// 月份标签 // 月份标签
if (firstDayOfMonth != null) { if (firstDayOfMonth != null) {
Text( AppText(
text = "${firstDayOfMonth.monthValue}", text = "${firstDayOfMonth.monthValue}",
fontSize = 10.sp, fontSize = 10.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -280,9 +281,9 @@ fun HeatmapLegend(
) { ) {
val legendUnit = if (mode == HeatmapMode.COUNT) "" else "" val legendUnit = if (mode == HeatmapMode.COUNT) "" else ""
Text( AppText(
"少($legendUnit)", "少($legendUnit)",
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = Color.Gray color = Color.Gray
) )
@@ -298,9 +299,9 @@ fun HeatmapLegend(
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
} }
Text( AppText(
"多($legendUnit)", "多($legendUnit)",
style = MaterialTheme.typography.bodySmall, style = LegadoTheme.typography.bodySmall,
color = Color.Gray 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 package io.legado.app.ui.widget.components.importComponents
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -26,26 +22,18 @@ import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip import androidx.compose.material3.AssistChip
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedToggleButton import androidx.compose.material3.OutlinedToggleButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -59,9 +47,13 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.AnimatedText import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AnimatedText
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@@ -77,25 +69,25 @@ fun SourceInputDialog(
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = { Text(title) }, title = { AppText(title) },
text = { text = {
Column { Column {
OutlinedTextField( OutlinedTextField(
value = text, value = text,
onValueChange = { text = it }, onValueChange = { text = it },
label = { Text(hint) }, label = { AppText(hint) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
maxLines = 5 maxLines = 5
) )
if (historyValues.isNotEmpty()) { if (historyValues.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text("历史记录:", style = MaterialTheme.typography.labelSmall) AppText("历史记录:", style = LegadoTheme.typography.labelSmall)
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
items(historyValues) { history -> items(historyValues) { history ->
AssistChip( AssistChip(
onClick = { text = history }, onClick = { text = history },
label = { Text(history, maxLines = 1) } label = { AppText(history, maxLines = 1) }
) )
} }
} }
@@ -105,41 +97,52 @@ fun SourceInputDialog(
confirmButton = { confirmButton = {
OutlinedButton( OutlinedButton(
onClick = { if (text.isNotBlank()) onConfirm(text) } onClick = { if (text.isNotBlank()) onConfirm(text) }
) { Text(stringResource(android.R.string.ok)) } ) { AppText(stringResource(android.R.string.ok)) }
}, },
dismissButton = { dismissButton = {
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
Text(stringResource(android.R.string.cancel)) AppText(stringResource(android.R.string.cancel))
} }
} }
) )
} }
//TODO: 动画
@SuppressLint("ConfigurationScreenWidthHeight") @SuppressLint("ConfigurationScreenWidthHeight")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun <T> BatchImportDialog( fun <T> BatchImportDialog(
title: String, title: String,
importState: BaseImportUiState.Success<T>, importState: BaseImportUiState<T>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onConfirm: (List<T>) -> Unit, // 返回选中的原始数据列表 onConfirm: (List<T>) -> Unit,
onToggleItem: (index: Int) -> Unit, onToggleItem: (index: Int) -> Unit,
onToggleAll: (isSelected: Boolean) -> Unit, onToggleAll: (isSelected: Boolean) -> Unit,
onItemInfoClick: (index: Int) -> Unit = {}, onItemInfoClick: (index: Int) -> Unit = {},
// 插槽:允许调用方自定义顶部菜单
topBarActions: @Composable RowScope.() -> Unit = {}, topBarActions: @Composable RowScope.() -> Unit = {},
// 插槽:自定义每一行的显示内容 itemTitle: (data: T) -> String,
itemContent: @Composable (data: T, status: ImportStatus) -> Unit itemSubtitle: (data: T) -> String? = { null }
) { ) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val show = importState is BaseImportUiState.Success<T>
var cachedState by remember { mutableStateOf<BaseImportUiState.Success<T>?>(null) }
if (importState is BaseImportUiState.Success<T>) {
cachedState = importState
}
if (!show && cachedState == null) return
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val selectedCount = importState.items.count { it.isSelected }
val totalCount = importState.items.size
GlassModalBottomSheet( val currentState = cachedState!!
val selectedCount = currentState.items.count { it.isSelected }
val totalCount = currentState.items.size
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
containerColor = { it.surfaceContainer } containerColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
AppScaffold( AppScaffold(
modifier = Modifier modifier = Modifier
@@ -170,44 +173,34 @@ fun <T> BatchImportDialog(
ImportBottomBar( ImportBottomBar(
selectedCount = selectedCount, selectedCount = selectedCount,
totalCount = totalCount, totalCount = totalCount,
onToggleSelectAll = { isAll -> onToggleAll(isAll) }, onToggleSelectAll = onToggleAll,
onConfirm = { onConfirm = {
scope.launch { sheetState.hide() }.invokeOnCompletion { val selectedData =
if (!sheetState.isVisible) { currentState.items.filter { it.isSelected }.map { it.data }
val selectedData = onConfirm(selectedData)
importState.items.filter { it.isSelected }.map { it.data }
onConfirm(selectedData)
}
}
}, },
onCancel = { onCancel = onDismissRequest
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
onDismissRequest()
}
}
}
) )
} }
) { padding -> ) { padding ->
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier.padding(padding),
.padding(padding),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
itemsIndexed(importState.items, key = { _, item -> item.data.hashCode() }) { index, itemWrapper -> itemsIndexed(
currentState.items,
key = { _, item -> item.data.hashCode() }) { index, itemWrapper ->
ImportItemRow( ImportItemRow(
title = itemTitle(itemWrapper.data),
subtitle = itemSubtitle(itemWrapper.data),
isSelected = itemWrapper.isSelected, isSelected = itemWrapper.isSelected,
status = itemWrapper.status, status = itemWrapper.status,
onClick = { onToggleItem(index) }, onClick = { onToggleItem(index) },
onInfoClick = { onInfoClick = {
scope.launch { scope.launch { snackbarHostState.showSnackbar("其实还没写桀桀桀") }
snackbarHostState.showSnackbar("其实还没写桀桀桀")
}
onItemInfoClick(index) onItemInfoClick(index)
}, }
content = { itemContent(itemWrapper.data, itemWrapper.status) }
) )
} }
} }
@@ -215,74 +208,47 @@ fun <T> BatchImportDialog(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ImportItemRow( fun ImportItemRow(
title: String,
subtitle: String? = null,
isSelected: Boolean, isSelected: Boolean,
status: ImportStatus, status: ImportStatus,
onClick: () -> Unit, onClick: () -> Unit,
onInfoClick: () -> Unit, onInfoClick: () -> Unit
content: @Composable () -> Unit
) { ) {
val containerColor by animateColorAsState( SelectionItemCard(
targetValue = if (isSelected) title = title,
MaterialTheme.colorScheme.secondaryContainer subtitle = subtitle,
else isSelected = isSelected,
MaterialTheme.colorScheme.surface, inSelectionMode = true,
animationSpec = tween( onToggleSelection = onClick,
durationMillis = 200, modifier = Modifier.padding(vertical = 4.dp),
easing = FastOutSlowInEasing trailingAction = {
), AppText(
label = "CardColor" text = when (status) {
) ImportStatus.New -> "新增"
ImportStatus.Update -> "更新"
Card( ImportStatus.Existing -> "已有"
onClick = onClick, ImportStatus.Error -> "错误"
modifier = Modifier.fillMaxWidth(), },
shape = MaterialTheme.shapes.medium, style = LegadoTheme.typography.labelMedium,
colors = CardDefaults.cardColors( color = when (status) {
containerColor = containerColor ImportStatus.New -> MaterialTheme.colorScheme.primary
) ImportStatus.Update -> MaterialTheme.colorScheme.secondary
) { ImportStatus.Error -> MaterialTheme.colorScheme.error
ListItem( else -> MaterialTheme.colorScheme.outline
modifier = Modifier.animateContentSize(), },
headlineContent = { content() }, modifier = Modifier.padding(end = 4.dp)
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
) )
)
} SmallIconButton(
onClick = onInfoClick,
icon = Icons.Default.Info,
contentDescription = "详情"
)
}
)
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -315,12 +281,12 @@ fun ImportBottomBar(
) )
} }
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedButton(onClick = onCancel) { Text("取消") } OutlinedButton(onClick = onCancel) { AppText("取消") }
Button( Button(
enabled = selectedCount > 0, enabled = selectedCount > 0,
onClick = onConfirm 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.fastForEach
import androidx.compose.ui.util.fastLastOrNull import androidx.compose.ui.util.fastLastOrNull
import androidx.compose.ui.util.fastMaxBy import androidx.compose.ui.util.fastMaxBy
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -67,7 +68,7 @@ fun VerticalFastScroller(
listState: LazyListState, listState: LazyListState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
thumbAllowed: () -> Boolean = { true }, thumbAllowed: () -> Boolean = { true },
thumbColor: Color = MaterialTheme.colorScheme.primary, thumbColor: Color = LegadoTheme.colorScheme.primary,
topContentPadding: Dp = Dp.Hairline, topContentPadding: Dp = Dp.Hairline,
bottomContentPadding: Dp = Dp.Hairline, bottomContentPadding: Dp = Dp.Hairline,
endContentPadding: Dp = Dp.Hairline, endContentPadding: Dp = Dp.Hairline,
@@ -15,19 +15,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState 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.animateFloatingActionButton
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment 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.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppScaffold 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.SelectionActions
import io.legado.app.ui.widget.components.SelectionBottomBar 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.DynamicTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarScrollBehavior
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -52,28 +46,21 @@ fun <T> ListScaffold(
onSearchQueryChange: (String) -> Unit, onSearchQueryChange: (String) -> Unit,
searchPlaceholder: String = "搜索...", searchPlaceholder: String = "搜索...",
topBarActions: @Composable RowScope.() -> Unit = {}, topBarActions: @Composable RowScope.() -> Unit = {},
bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null, bottomContent: @Composable (ColumnScope.(GlassTopAppBarScrollBehavior) -> Unit)? = null,
dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null, dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
selectionActions: SelectionActions? = null, selectionActions: SelectionActions? = null,
onAddClick: (() -> Unit)? = null, onAddClick: (() -> Unit)? = null,
floatingActionButton: @Composable () -> Unit = { floatingActionButton: @Composable () -> Unit = {
onAddClick?.let { onClick -> onAddClick?.let { onClick ->
TooltipBox( AppFloatingActionButton(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider( onClick = onClick,
TooltipAnchorPosition.Above modifier = Modifier.animateFloatingActionButton(
visible = state.selectedIds.isEmpty(),
alignment = Alignment.BottomEnd,
), ),
tooltip = { PlainTooltip { Text("添加") } }, tooltipText = "添加"
state = rememberTooltipState(),
) { ) {
FloatingActionButton( Icon(Icons.Default.Add, contentDescription = "Add")
modifier = Modifier.animateFloatingActionButton(
visible = state.selectedIds.isEmpty(),
alignment = Alignment.BottomEnd,
),
onClick = onClick
) {
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.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.MaterialExpressiveTheme
@@ -15,8 +17,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.rememberOpaqueColorScheme import io.legado.app.ui.theme.rememberOpaqueColorScheme
import top.yukonga.miuix.kmp.basic.ListPopupColumn
import top.yukonga.miuix.kmp.extra.SuperListPopup
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -24,31 +29,50 @@ fun RoundDropdownMenu(
expanded: Boolean, expanded: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
// M3 专属视觉参数,Miuix 模式下会优雅降级(忽略)
shape: Shape = MaterialTheme.shapes.medium, shape: Shape = MaterialTheme.shapes.medium,
shadowElevation: Dp = 4.dp, shadowElevation: Dp = 4.dp,
verticalSpacing: Dp = 8.dp, verticalSpacing: Dp = 8.dp,
content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit
) { ) {
val colorScheme = rememberOpaqueColorScheme() val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
DropdownMenu( if (isMiuix) {
expanded = expanded, SuperListPopup(
onDismissRequest = onDismissRequest, show = expanded,
modifier = modifier, onDismissRequest = onDismissRequest,
shape = shape, popupModifier = modifier
shadowElevation = shadowElevation,
containerColor = colorScheme.surfaceContainerLow
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) { ) {
Column( ListPopupColumn {
verticalArrangement = Arrangement.spacedBy(verticalSpacing) 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 package io.legado.app.ui.widget.components.menuItem
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -11,53 +13,114 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuDefaults import androidx.compose.material3.MenuDefaults
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.rememberOpaqueColorScheme import io.legado.app.ui.theme.rememberOpaqueColorScheme
import io.legado.app.ui.widget.components.icon.AppIcon
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
fun RoundDropdownMenuItem( fun RoundDropdownMenuItem(
text: @Composable () -> Unit, text: String,
color: Color = LegadoTheme.colorScheme.surface,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isSelected: Boolean = false,
leadingIcon: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null,
enabled: Boolean = true, enabled: Boolean = true,
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding, contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
interactionSource: MutableInteractionSource? = null, interactionSource: MutableInteractionSource? = null,
) { ) {
val colorScheme = rememberOpaqueColorScheme() val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
val interaction = interactionSource ?: remember { MutableInteractionSource() } val interaction = interactionSource ?: remember { MutableInteractionSource() }
val contentColor = if (isMiuix) {
if (enabled) MaterialTheme.colorScheme.onSurface val backgroundColor = if (isSelected) {
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) LegadoTheme.colorScheme.primaryContainer
} else {
Color.Transparent
}
Surface( val contentColor = if (isSelected) {
onClick = onClick, LegadoTheme.colorScheme.onPrimaryContainer
modifier = modifier } else {
.padding(horizontal = 8.dp) LegadoTheme.colorScheme.onSurface
.fillMaxWidth(), }
enabled = enabled,
shape = MaterialTheme.shapes.small, Row(
color = colorScheme.surface, verticalAlignment = Alignment.CenterVertically,
interactionSource = interaction horizontalArrangement = Arrangement.SpaceBetween,
) { modifier = modifier
CompositionLocalProvider( .fillMaxWidth()
LocalContentColor provides contentColor, .drawBehind { drawRect(backgroundColor) }
LocalTextStyle provides MaterialTheme.typography.labelLarge .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( Row(
modifier = Modifier modifier = Modifier
@@ -66,17 +129,20 @@ fun RoundDropdownMenuItem(
.widthIn(min = 120.dp), .widthIn(min = 120.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (leadingIcon != null) { if (leadingIcon != null) {
leadingIcon() leadingIcon()
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(12.dp))
} }
Box( Box(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
contentAlignment = Alignment.CenterStart contentAlignment = Alignment.CenterStart
) { ) {
text() Text(
modifier = Modifier.widthIn(max = 200.dp),
text = text,
style = LegadoTheme.typography.bodyMediumEmphasized
)
} }
if (trailingIcon != null) { if (trailingIcon != null) {
@@ -93,9 +159,9 @@ fun MenuItemIcon(
imageVector: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null, contentDescription: String? = null,
tint: Color = LocalContentColor.current tint: Color = Color.Unspecified
) { ) {
Icon( AppIcon(
imageVector = imageVector, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = modifier.size(18.dp), modifier = modifier.size(18.dp),
@@ -0,0 +1,138 @@
package io.legado.app.ui.widget.components.modalBottomSheet
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.Text
import androidx.compose.material3.Typography
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.rememberOpaqueColorScheme
import top.yukonga.miuix.kmp.extra.WindowBottomSheet
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AppModalBottomSheet(
show: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
title: String? = null,
startAction: @Composable (() -> Unit)? = null,
endAction: @Composable (() -> Unit)? = null,
containerColor: Color = LegadoTheme.colorScheme.surfaceContainerLow,
content: @Composable ColumnScope.() -> Unit
) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
WindowBottomSheet(
show = show,
modifier = modifier,
title = title,
startAction = startAction,
endAction = endAction,
insideMargin = DpSize(16.dp, 12.dp),
backgroundColor = containerColor,
onDismissRequest = onDismissRequest,
onDismissFinished = onDismissRequest,
enableWindowDim = true,
allowDismiss = true
) {
Column(
modifier = Modifier.fillMaxWidth(),
content = content
)
}
} else {
if (show) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val colorScheme = rememberOpaqueColorScheme()
val density = LocalDensity.current
val maxHeight = with(density) {
LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
}
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
containerColor = containerColor,
contentColor = colorScheme.onSurface,
dragHandle = { BottomSheetDefaults.DragHandle() }
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.heightIn(max = maxHeight)
.animateContentSize()
.then(modifier)
) {
val hasHeader =
!title.isNullOrEmpty() || startAction != null || endAction != null
if (hasHeader) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
contentAlignment = Alignment.Center
) {
if (startAction != null) {
Box(modifier = Modifier.align(Alignment.CenterStart)) {
startAction()
}
}
if (!title.isNullOrEmpty()) {
Text(
text = title,
style = LegadoTheme.typography.titleMediumEmphasized,
color = colorScheme.onSurface,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 56.dp)
)
}
if (endAction != null) {
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
endAction()
}
}
}
}
content()
}
}
}
}
}
}
@@ -1,64 +0,0 @@
package io.legado.app.ui.widget.components.modalBottomSheet
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.SheetState
import androidx.compose.material3.Typography
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import io.legado.app.ui.theme.rememberOpaqueColorScheme
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun GlassModalBottomSheet(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
sheetState: SheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
containerColor: (ColorScheme) -> Color = { it.surfaceContainerLow },
content: @Composable ColumnScope.() -> Unit
) {
val colorScheme = rememberOpaqueColorScheme()
val density = LocalDensity.current
val maxHeight = with(density) {
LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
}
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
containerColor = containerColor(colorScheme),
contentColor = colorScheme.onSurface
) {
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = Typography(),
motionScheme = MotionScheme.expressive(),
shapes = Shapes()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = maxHeight)
.animateContentSize()
.then(modifier),
content = content
)
}
}
}
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -52,6 +52,7 @@ data class RuleEditFields(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun <T> RuleEditSheet( fun <T> RuleEditSheet(
show: Boolean,
rule: T?, rule: T?,
title: String, title: String,
label1: String, label1: String,
@@ -74,7 +75,8 @@ fun <T> RuleEditSheet(
fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule) fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
GlassModalBottomSheet( AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
@@ -84,7 +86,7 @@ fun <T> RuleEditSheet(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = { Text(title) }, title = { AppText(title) },
navigationIcon = { navigationIcon = {
IconButton(onClick = onDismissRequest) { IconButton(onClick = onDismissRequest) {
Icon( Icon(
@@ -99,7 +101,7 @@ fun <T> RuleEditSheet(
} }
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.copy_rule)) }, text = { AppText(stringResource(R.string.copy_rule)) },
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) }, leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
onClick = { onClick = {
onCopy(getCurrentEntity()) onCopy(getCurrentEntity())
@@ -107,7 +109,7 @@ fun <T> RuleEditSheet(
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.paste_rule)) }, text = { AppText(stringResource(R.string.paste_rule)) },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) }, leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = { onClick = {
scope.launch { scope.launch {
@@ -136,20 +138,20 @@ fun <T> RuleEditSheet(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { Text(stringResource(R.string.name)) }, label = { AppText(stringResource(R.string.name)) },
singleLine = true singleLine = true
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = rule1, value = rule1,
onValueChange = { rule1 = it }, onValueChange = { rule1 = it },
label = { Text(label1) } label = { AppText(label1) }
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = rule2, value = rule2,
onValueChange = { rule2 = it }, onValueChange = { rule2 = it },
label = { Text(label2) }, label = { AppText(label2) },
minLines = 3 minLines = 3
) )
} }

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