Merge branch 'miuix-dev'
# Conflicts: # app/version.properties
This commit is contained in:
@@ -285,4 +285,9 @@ dependencies {
|
||||
implementation(libs.material.kolor)
|
||||
implementation(libs.haze.core)
|
||||
implementation(libs.haze.materials)
|
||||
implementation(libs.miuix)
|
||||
implementation(libs.miuix.icons)
|
||||
implementation(libs.capsule)
|
||||
implementation(libs.backdrop)
|
||||
implementation(libs.timber)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -113,6 +114,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -141,6 +143,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -169,6 +172,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -202,6 +206,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -236,6 +241,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -265,6 +271,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -293,6 +300,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -321,6 +329,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -350,6 +359,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -378,6 +388,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -406,6 +417,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -434,6 +446,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
@@ -462,6 +475,7 @@ interface BookDao {
|
||||
durChapterPos,
|
||||
latestChapterTitle,
|
||||
latestChapterTime,
|
||||
lastCheckCount,
|
||||
totalChapterNum,
|
||||
durChapterIndex,
|
||||
type,
|
||||
|
||||
@@ -15,9 +15,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -31,13 +28,15 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.AppConst.appInfo
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
import io.legado.app.ui.widget.components.settingItem.SettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -59,7 +58,7 @@ fun AboutScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(stringResource(R.string.about)) },
|
||||
title = stringResource(R.string.about),
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBack)
|
||||
},
|
||||
@@ -81,9 +80,9 @@ fun AboutScreen(
|
||||
.width(160.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
style = LegadoTheme.typography.bodyLarge,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
@@ -91,16 +90,14 @@ fun AboutScreen(
|
||||
.wrapContentWidth(Alignment.CenterHorizontally))
|
||||
TextCard(
|
||||
text = versionName,
|
||||
backgroundColor = colorScheme.tertiaryContainer,
|
||||
contentColor = colorScheme.onTertiaryContainer,
|
||||
cornerRadius = 8.dp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.CenterHorizontally)
|
||||
.padding(vertical = 4.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.about_description),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
style = LegadoTheme.typography.bodyLarge,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -31,19 +30,23 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.LogUtils
|
||||
import java.util.Date
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppLogSheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
var logs by remember { mutableStateOf(AppLog.logs) }
|
||||
var showStackTrace by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Row(
|
||||
@@ -52,9 +55,9 @@ fun AppLogSheet(
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.log),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
style = LegadoTheme.typography.titleLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
IconButton(onClick = {
|
||||
@@ -82,17 +85,17 @@ fun AppLogSheet(
|
||||
showStackTrace?.let { stackTrace ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showStackTrace = null },
|
||||
title = { Text("Log") },
|
||||
title = { AppText("Log") },
|
||||
text = {
|
||||
SelectionContainer {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
Text(text = stackTrace, style = MaterialTheme.typography.bodySmall)
|
||||
AppText(text = stackTrace, style = LegadoTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showStackTrace = null }) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
AppText(stringResource(android.R.string.ok))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -110,14 +113,14 @@ private fun LogItem(
|
||||
.clickable(onClick = onClick)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = LogUtils.logTimeFormat.format(Date(item.first)),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = item.second,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -41,8 +40,6 @@ import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.ui.widget.CollapsibleHeader
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SearchBarSection
|
||||
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
|
||||
import io.legado.app.ui.widget.components.bookmark.BookmarkItem
|
||||
@@ -51,6 +48,8 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
import io.legado.app.ui.widget.components.lazylist.Scroller
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
|
||||
@@ -98,11 +97,7 @@ fun AllBookmarkScreen(
|
||||
topBar = {
|
||||
Column {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = "所有书签"
|
||||
)
|
||||
},
|
||||
title = "所有书签",
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBack)
|
||||
@@ -132,7 +127,7 @@ fun AllBookmarkScreen(
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("导出 JSON") },
|
||||
text = "导出 JSON",
|
||||
onClick = {
|
||||
showMenu = false
|
||||
pendingExportIsMd = false
|
||||
@@ -140,7 +135,7 @@ fun AllBookmarkScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("导出 Markdown") },
|
||||
text = "导出 Markdown",
|
||||
onClick = {
|
||||
showMenu = false
|
||||
pendingExportIsMd = true
|
||||
@@ -242,22 +237,21 @@ fun AllBookmarkScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showBottomSheet && editingBookmark != null) {
|
||||
BookmarkEditSheet(
|
||||
bookmark = editingBookmark!!,
|
||||
onDismiss = {
|
||||
showBottomSheet = false
|
||||
editingBookmark = null
|
||||
},
|
||||
onSave = { updatedBookmark ->
|
||||
viewModel.updateBookmark(updatedBookmark)
|
||||
showBottomSheet = false
|
||||
},
|
||||
onDelete = { bookmarkToDelete ->
|
||||
viewModel.deleteBookmark(bookmarkToDelete)
|
||||
showBottomSheet = false
|
||||
}
|
||||
)
|
||||
}
|
||||
BookmarkEditSheet(
|
||||
show = showBottomSheet && editingBookmark != null,
|
||||
bookmark = editingBookmark ?: Bookmark(),
|
||||
onDismiss = {
|
||||
showBottomSheet = false
|
||||
editingBookmark = null
|
||||
},
|
||||
onSave = { updatedBookmark ->
|
||||
viewModel.updateBookmark(updatedBookmark)
|
||||
showBottomSheet = false
|
||||
},
|
||||
onDelete = { bookmarkToDelete ->
|
||||
viewModel.deleteBookmark(bookmarkToDelete)
|
||||
showBottomSheet = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
@@ -79,12 +78,10 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
import io.legado.app.data.entities.rule.ExploreKind
|
||||
import io.legado.app.model.BookShelfState
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.responsiveHazeEffect
|
||||
import io.legado.app.ui.theme.responsiveHazeSource
|
||||
import io.legado.app.ui.widget.components.AnimatedTextLine
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SearchBarSection
|
||||
import io.legado.app.ui.widget.components.button.AnimatedTextButton
|
||||
import io.legado.app.ui.widget.components.button.TopBarActionButton
|
||||
@@ -93,7 +90,10 @@ import io.legado.app.ui.widget.components.card.TextCard
|
||||
import io.legado.app.ui.widget.components.cover.Cover
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -180,112 +180,111 @@ fun ExploreShowScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showGridCountSheet) {
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = showGridCountSheet,
|
||||
modifier = Modifier
|
||||
.padding(16.dp),
|
||||
onDismissRequest = { showGridCountSheet = false }
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(16.dp),
|
||||
onDismissRequest = { showGridCountSheet = false }
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = "布局列数",
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
TextCard(
|
||||
text = "$gridColumnCount 列",
|
||||
textStyle = MaterialTheme.typography.titleSmall,
|
||||
verticalPadding = 4.dp,
|
||||
horizontalPadding = 12.dp,
|
||||
cornerRadius = 12.dp
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value = gridColumnCount.toFloat(),
|
||||
onValueChange = {
|
||||
val col = it.toInt().coerceIn(1, 10)
|
||||
viewModel.saveGridCount(col)
|
||||
},
|
||||
valueRange = 1f..10f,
|
||||
steps = 8,
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
AppText(
|
||||
text = "布局列数",
|
||||
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { showGridCountSheet = false },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text("完成")
|
||||
}
|
||||
TextCard(
|
||||
text = "$gridColumnCount 列",
|
||||
textStyle = LegadoTheme.typography.titleSmall,
|
||||
verticalPadding = 4.dp,
|
||||
horizontalPadding = 12.dp,
|
||||
cornerRadius = 12.dp
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value = gridColumnCount.toFloat(),
|
||||
onValueChange = {
|
||||
val col = it.toInt().coerceIn(1, 10)
|
||||
viewModel.saveGridCount(col)
|
||||
},
|
||||
valueRange = 1f..10f,
|
||||
steps = 8,
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { showGridCountSheet = false },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
AppText("完成")
|
||||
}
|
||||
}
|
||||
|
||||
if (showKindSheet) {
|
||||
GlassModalBottomSheet(
|
||||
onDismissRequest = { showKindSheet = false }
|
||||
) {
|
||||
|
||||
var kindQuery by remember { mutableStateOf("") }
|
||||
AppModalBottomSheet(
|
||||
show = showKindSheet,
|
||||
onDismissRequest = { showKindSheet = false }
|
||||
) {
|
||||
|
||||
SearchBarSection(
|
||||
query = kindQuery,
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
onQueryChange = { kindQuery = it },
|
||||
placeholder = "选择或搜索分类",
|
||||
)
|
||||
var kindQuery by remember { mutableStateOf("") }
|
||||
|
||||
val filteredKinds = remember(kindQuery, kinds) {
|
||||
if (kindQuery.isBlank()) kinds
|
||||
else kinds.filter { kind ->
|
||||
kind.title.contains(kindQuery, ignoreCase = true) ||
|
||||
(kind.url?.contains(kindQuery, ignoreCase = true) == true)
|
||||
}
|
||||
SearchBarSection(
|
||||
query = kindQuery,
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
onQueryChange = { kindQuery = it },
|
||||
placeholder = "选择或搜索分类",
|
||||
)
|
||||
|
||||
val filteredKinds = remember(kindQuery, kinds) {
|
||||
if (kindQuery.isBlank()) kinds
|
||||
else kinds.filter { kind ->
|
||||
kind.title.contains(kindQuery, ignoreCase = true) ||
|
||||
(kind.url?.contains(kindQuery, ignoreCase = true) == true)
|
||||
}
|
||||
}
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.weight(1f, fill = false)
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = filteredKinds,
|
||||
key = { index, kind -> "${kind.url ?: kind.title}_$index" },
|
||||
span = { _, kind ->
|
||||
val isClickable = !kind.url.isNullOrBlank()
|
||||
if (isClickable) GridItemSpan(1) else GridItemSpan(3)
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.weight(1f, fill = false)
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = filteredKinds,
|
||||
key = { index, kind -> "${kind.url ?: kind.title}_$index" },
|
||||
span = { _, kind ->
|
||||
val isClickable = !kind.url.isNullOrBlank()
|
||||
if (isClickable) GridItemSpan(1) else GridItemSpan(3)
|
||||
}
|
||||
) { _, kind ->
|
||||
KindGridItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
kind = kind,
|
||||
currentTitle = selectedTitle ?: title,
|
||||
onClick = {
|
||||
showKindSheet = false
|
||||
viewModel.switchExploreUrl(kind)
|
||||
}
|
||||
) { _, kind ->
|
||||
KindGridItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
kind = kind,
|
||||
currentTitle = selectedTitle ?: title,
|
||||
onClick = {
|
||||
showKindSheet = false
|
||||
viewModel.switchExploreUrl(kind)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AppScaffold(
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
@@ -294,14 +293,7 @@ fun ExploreShowScreen(
|
||||
modifier = Modifier.responsiveHazeEffect(
|
||||
state = hazeState
|
||||
),
|
||||
title = {
|
||||
AnimatedTextLine(
|
||||
selectedTitle ?: title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
colors = GlassTopAppBarDefaults.glassColors(),
|
||||
title = selectedTitle ?: title,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBack)
|
||||
},
|
||||
@@ -346,7 +338,7 @@ fun ExploreShowScreen(
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("全部显示") },
|
||||
text = "全部显示",
|
||||
onClick = {
|
||||
viewModel.setFilterState(BookFilterState.SHOW_ALL)
|
||||
showMenu = false
|
||||
@@ -358,7 +350,7 @@ fun ExploreShowScreen(
|
||||
)
|
||||
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("隐藏已在书架的同源书籍") },
|
||||
text = "隐藏已在书架的同源书籍",
|
||||
onClick = {
|
||||
viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
|
||||
showMenu = false
|
||||
@@ -370,7 +362,7 @@ fun ExploreShowScreen(
|
||||
)
|
||||
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("隐藏已在书架的非同源书籍") },
|
||||
text = "隐藏已在书架的非同源书籍",
|
||||
onClick = {
|
||||
viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
|
||||
showMenu = false
|
||||
@@ -382,7 +374,7 @@ fun ExploreShowScreen(
|
||||
)
|
||||
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("只显示不在书架的书籍") },
|
||||
text = "只显示不在书架的书籍",
|
||||
onClick = {
|
||||
viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
|
||||
showMenu = false
|
||||
@@ -509,7 +501,7 @@ fun KindGridItem(
|
||||
enabled = isClickable,
|
||||
selected = isSelected,
|
||||
label = {
|
||||
Text(
|
||||
AppText(
|
||||
text = kind.title,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -576,32 +568,32 @@ fun ExploreBookItem(
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)) {
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = book.name,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
style = LegadoTheme.typography.titleSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
Row {
|
||||
Text(
|
||||
AppText(
|
||||
text = book.author,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
val latestChapter = book.latestChapterTitle
|
||||
if (!latestChapter.isNullOrEmpty()) {
|
||||
Text(
|
||||
AppText(
|
||||
text = " • ",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = "最新: $latestChapter",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -612,9 +604,9 @@ fun ExploreBookItem(
|
||||
|
||||
val intro = book.intro?.replace("\\s+".toRegex(), "") ?: ""
|
||||
if (intro.isNotEmpty()) {
|
||||
Text(
|
||||
AppText(
|
||||
text = intro,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
color = Color.Gray,
|
||||
maxLines = 2,
|
||||
minLines = 2,
|
||||
@@ -654,9 +646,12 @@ fun ExploreBookGridItem(
|
||||
|
||||
val content: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) {
|
||||
{
|
||||
Text(
|
||||
AppText(
|
||||
text = badgeText,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold, fontSize = 9.sp)
|
||||
style = LegadoTheme.typography.labelSmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 9.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -681,9 +676,9 @@ fun ExploreBookGridItem(
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = book.name,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -698,10 +693,10 @@ fun TagChip(text: String) {
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = text,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
@@ -742,13 +737,13 @@ fun LoadMoreFooter(
|
||||
},
|
||||
label = "FooterTextChange"
|
||||
) { text ->
|
||||
Text(
|
||||
AppText(
|
||||
text = text,
|
||||
color = when {
|
||||
errorMsg != null -> Color.Red
|
||||
else -> Color.Gray
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
style = LegadoTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -18,11 +17,9 @@ import androidx.compose.material.icons.filled.Restore
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedIconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
@@ -39,9 +36,10 @@ import io.legado.app.R
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.lib.dialogs.alert
|
||||
import io.legado.app.ui.widget.components.cover.Cover
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.FileUtils
|
||||
import io.legado.app.utils.MD5Utils
|
||||
import io.legado.app.utils.SelectImageContract
|
||||
@@ -55,11 +53,12 @@ import java.io.FileOutputStream
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GroupEditSheet(
|
||||
show: Boolean,
|
||||
group: BookGroup? = null,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: GroupViewModel = koinViewModel()
|
||||
) {
|
||||
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
|
||||
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
|
||||
GroupEditContent(
|
||||
group = group,
|
||||
onDismissRequest = onDismissRequest,
|
||||
@@ -108,17 +107,9 @@ fun GroupEditContent(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.group_edit),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
@@ -138,7 +129,7 @@ fun GroupEditContent(
|
||||
OutlinedTextField(
|
||||
value = groupName,
|
||||
onValueChange = { groupName = it },
|
||||
label = { Text(stringResource(R.string.group_name)) },
|
||||
label = { AppText(stringResource(R.string.group_name)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
@@ -206,7 +197,7 @@ fun GroupEditContent(
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
Button(onClick = {
|
||||
if (groupName.isEmpty()) {
|
||||
@@ -235,7 +226,7 @@ fun GroupEditContent(
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
@@ -80,6 +79,7 @@ import io.legado.app.data.entities.Book
|
||||
import io.legado.app.data.entities.Server
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.model.remote.RemoteBook
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.ActionItem
|
||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||
import io.legado.app.ui.widget.components.SelectionActions
|
||||
@@ -88,7 +88,8 @@ import io.legado.app.ui.widget.components.button.SmallTonalIconButton
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
import io.legado.app.ui.widget.components.list.ListScaffold
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.ArchiveUtils
|
||||
import io.legado.app.utils.ConvertUtils
|
||||
import io.legado.app.utils.FileDoc
|
||||
@@ -157,19 +158,19 @@ fun RemoteBookScreen(
|
||||
is RemoteBookDialog.DownloadArchive -> {
|
||||
AlertDialog(
|
||||
onDismissRequest = { dialogState = null },
|
||||
title = { Text(stringResource(R.string.draw)) },
|
||||
text = { Text(stringResource(R.string.archive_not_found)) },
|
||||
title = { AppText(stringResource(R.string.draw)) },
|
||||
text = { AppText(stringResource(R.string.archive_not_found)) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
|
||||
dialogState = null
|
||||
}) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
AppText(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { dialogState = null }) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -178,19 +179,19 @@ fun RemoteBookScreen(
|
||||
is RemoteBookDialog.ReImport -> {
|
||||
AlertDialog(
|
||||
onDismissRequest = { dialogState = null },
|
||||
title = { Text("是否重新加入书架?") },
|
||||
text = { Text("将会覆盖书籍") },
|
||||
title = { AppText("是否重新加入书架?") },
|
||||
text = { AppText("将会覆盖书籍") },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
scope.launch { viewModel.addToBookshelf(setOf(state.remoteBook)) }
|
||||
dialogState = null
|
||||
}) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
AppText(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { dialogState = null }) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -198,42 +199,41 @@ fun RemoteBookScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showSheet != null) {
|
||||
GlassModalBottomSheet(
|
||||
onDismissRequest = { showSheet = null }
|
||||
) {
|
||||
when (val state = showSheet) {
|
||||
is RemoteBookSheet.Servers -> {
|
||||
ServersSheetContent(
|
||||
servers = uiState.servers,
|
||||
selectedServerId = uiState.selectedServerId,
|
||||
onSelect = {
|
||||
viewModel.selectServer(it)
|
||||
showSheet = null
|
||||
},
|
||||
onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
|
||||
onDelete = { viewModel.deleteServer(it) },
|
||||
onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
|
||||
onDefault = {
|
||||
viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
|
||||
showSheet = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is RemoteBookSheet.ServerConfig -> {
|
||||
ServerConfigSheetContent(
|
||||
server = state.server,
|
||||
onSave = {
|
||||
viewModel.saveServer(it)
|
||||
showSheet = RemoteBookSheet.Servers
|
||||
},
|
||||
onCancel = { showSheet = RemoteBookSheet.Servers }
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
AppModalBottomSheet(
|
||||
show = showSheet != null,
|
||||
onDismissRequest = { showSheet = null }
|
||||
) {
|
||||
when (val state = showSheet) {
|
||||
is RemoteBookSheet.Servers -> {
|
||||
ServersSheetContent(
|
||||
servers = uiState.servers,
|
||||
selectedServerId = uiState.selectedServerId,
|
||||
onSelect = {
|
||||
viewModel.selectServer(it)
|
||||
showSheet = null
|
||||
},
|
||||
onEdit = { showSheet = RemoteBookSheet.ServerConfig(it) },
|
||||
onDelete = { viewModel.deleteServer(it) },
|
||||
onAdd = { showSheet = RemoteBookSheet.ServerConfig(null) },
|
||||
onDefault = {
|
||||
viewModel.selectServer(AppConst.DEFAULT_WEBDAV_ID)
|
||||
showSheet = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is RemoteBookSheet.ServerConfig -> {
|
||||
ServerConfigSheetContent(
|
||||
server = state.server,
|
||||
onSave = {
|
||||
viewModel.saveServer(it)
|
||||
showSheet = RemoteBookSheet.Servers
|
||||
},
|
||||
onCancel = { showSheet = RemoteBookSheet.Servers }
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ fun RemoteBookScreen(
|
||||
},
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("按名称排序") },
|
||||
text = "按名称排序",
|
||||
onClick = {
|
||||
viewModel.toggleSort(RemoteBookSort.Name)
|
||||
dismiss()
|
||||
@@ -271,7 +271,7 @@ fun RemoteBookScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("按时间排序") },
|
||||
text = "按时间排序",
|
||||
onClick = {
|
||||
viewModel.toggleSort(RemoteBookSort.Default)
|
||||
dismiss()
|
||||
@@ -381,9 +381,9 @@ private fun ServersSheetContent(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.server_config),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
style = LegadoTheme.typography.titleLarge
|
||||
)
|
||||
IconButton(onClick = onAdd) {
|
||||
Icon(Icons.Default.Add, contentDescription = "添加")
|
||||
@@ -447,9 +447,9 @@ private fun ServerItem(
|
||||
ListItem(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
headlineContent = {
|
||||
Text(
|
||||
AppText(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -462,9 +462,9 @@ private fun ServerItem(
|
||||
},
|
||||
supportingContent = url?.let {
|
||||
{
|
||||
Text(
|
||||
AppText(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -514,16 +514,16 @@ private fun ServerConfigSheetContent(
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = if (server == null) "添加服务器" else "编辑服务器",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
style = LegadoTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text("名称") },
|
||||
label = { AppText("名称") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
@@ -531,7 +531,7 @@ private fun ServerConfigSheetContent(
|
||||
OutlinedTextField(
|
||||
value = url,
|
||||
onValueChange = { url = it },
|
||||
label = { Text("URL") },
|
||||
label = { AppText("URL") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
@@ -539,7 +539,7 @@ private fun ServerConfigSheetContent(
|
||||
OutlinedTextField(
|
||||
value = username,
|
||||
onValueChange = { username = it },
|
||||
label = { Text("用户名") },
|
||||
label = { AppText("用户名") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
@@ -547,7 +547,7 @@ private fun ServerConfigSheetContent(
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
label = { Text("密码") },
|
||||
label = { AppText("密码") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
@@ -571,7 +571,7 @@ private fun ServerConfigSheetContent(
|
||||
onClick = onCancel,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
@@ -587,7 +587,7 @@ private fun ServerConfigSheetContent(
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = name.isNotBlank() && url.isNotBlank()
|
||||
) {
|
||||
Text("保存")
|
||||
AppText("保存")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -625,9 +625,9 @@ private fun PathNavigationBar(
|
||||
itemsIndexed(pathNames) { index, name ->
|
||||
val isLast = index == pathNames.lastIndex
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium,
|
||||
color = if (isLast)
|
||||
MaterialTheme.colorScheme.primary
|
||||
@@ -716,9 +716,9 @@ private fun RemoteBookItem(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = book.filename.substringBeforeLast("."),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -732,7 +732,7 @@ private fun RemoteBookItem(
|
||||
if (book.contentType.isNotEmpty()) {
|
||||
TextCard(
|
||||
text = book.contentType.uppercase(),
|
||||
textStyle = MaterialTheme.typography.labelSmall,
|
||||
textStyle = LegadoTheme.typography.labelSmall,
|
||||
horizontalPadding = 4.dp,
|
||||
verticalPadding = 2.dp,
|
||||
cornerRadius = 4.dp,
|
||||
@@ -743,11 +743,11 @@ private fun RemoteBookItem(
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = "${ConvertUtils.formatFileSize(book.size)} • ${
|
||||
AppConst.dateFormat.format(book.lastModify)
|
||||
}",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -47,12 +46,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.book.changecover.ChangeCoverDialog
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.button.TopBarButtonVariant
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.cover.Cover
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.utils.SelectImageContract
|
||||
import io.legado.app.utils.launch
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
@@ -71,7 +71,7 @@ fun BookInfoEditScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.book_info_edit)) },
|
||||
title = stringResource(id = R.string.book_info_edit),
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(
|
||||
onClick = onBack,
|
||||
@@ -191,35 +191,35 @@ fun BookInfoEditContent(
|
||||
OutlinedTextField(
|
||||
value = uiState.name,
|
||||
onValueChange = { viewModel.onNameChange(it) },
|
||||
label = { Text("书名") },
|
||||
label = { AppText("书名") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.author,
|
||||
onValueChange = { viewModel.onAuthorChange(it) },
|
||||
label = { Text("作者") },
|
||||
label = { AppText("作者") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.coverUrl ?: "",
|
||||
onValueChange = { viewModel.onCoverUrlChange(it) },
|
||||
label = { Text("封面链接") },
|
||||
label = { AppText("封面链接") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.intro ?: "",
|
||||
onValueChange = { viewModel.onIntroChange(it) },
|
||||
label = { Text("简介") },
|
||||
label = { AppText("简介") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.remark ?: "",
|
||||
onValueChange = { viewModel.onRemarkChange(it) },
|
||||
label = { Text("备注") },
|
||||
label = { AppText("备注") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -251,7 +251,7 @@ fun BookTypeDropdown(
|
||||
state = textFieldState,
|
||||
readOnly = true,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
label = { Text("书籍类型") },
|
||||
label = { AppText("书籍类型") },
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
|
||||
},
|
||||
@@ -269,7 +269,7 @@ fun BookTypeDropdown(
|
||||
) {
|
||||
bookTypes.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(option) },
|
||||
text = { AppText(option) },
|
||||
onClick = {
|
||||
onTypeSelected(option)
|
||||
expanded = false
|
||||
|
||||
@@ -32,18 +32,13 @@ import androidx.compose.material.icons.filled.Schedule
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Timeline
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -78,15 +73,15 @@ import androidx.compose.ui.zIndex
|
||||
import cn.hutool.core.date.DateUtil
|
||||
import io.legado.app.data.entities.readRecord.ReadRecord
|
||||
import io.legado.app.data.entities.readRecord.ReadRecordDetail
|
||||
import io.legado.app.ui.widget.components.AnimatedTextLine
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.CollapsibleHeader
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SearchBarSection
|
||||
import io.legado.app.ui.widget.components.SectionHeader
|
||||
import io.legado.app.ui.widget.components.button.AlertButton
|
||||
import io.legado.app.ui.widget.components.button.AppIconButton
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
import io.legado.app.ui.widget.components.cover.Cover
|
||||
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar
|
||||
import io.legado.app.ui.widget.components.heatmap.HeatmapConfig
|
||||
@@ -98,9 +93,14 @@ import io.legado.app.ui.widget.components.heatmap.WeekdayLabelsColumn
|
||||
import io.legado.app.ui.widget.components.heatmap.rememberDateRange
|
||||
import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange
|
||||
import io.legado.app.ui.widget.components.heatmap.rememberWeeks
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.swipe.SwipeAction
|
||||
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
|
||||
import io.legado.app.ui.widget.components.text.AnimatedTextLine
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.utils.StringUtils.formatFriendlyDate
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -148,11 +148,7 @@ fun ReadRecordScreen(
|
||||
topBar = {
|
||||
Column {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = "阅读记录"
|
||||
)
|
||||
},
|
||||
title = "阅读记录",
|
||||
subtitle = {
|
||||
val subTitle = when (displayMode) {
|
||||
DisplayMode.AGGREGATE -> "汇总视图"
|
||||
@@ -167,7 +163,7 @@ fun ReadRecordScreen(
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
AppIconButton(onClick = {
|
||||
val newMode = when (displayMode) {
|
||||
DisplayMode.AGGREGATE -> DisplayMode.TIMELINE
|
||||
DisplayMode.TIMELINE -> DisplayMode.LATEST
|
||||
@@ -181,13 +177,16 @@ fun ReadRecordScreen(
|
||||
DisplayMode.LATEST -> Icons.AutoMirrored.Filled.List
|
||||
}
|
||||
val description = if (displayMode == DisplayMode.AGGREGATE) "Switch to Timeline" else "Switch to Aggregate"
|
||||
Icon(icon, description)
|
||||
AppIcon(icon, description)
|
||||
}
|
||||
IconButton(onClick = { showCalendar = !showCalendar }) {
|
||||
Icon(Icons.Default.CalendarMonth, contentDescription = "Toggle Calendar")
|
||||
AppIconButton(onClick = { showCalendar = !showCalendar }) {
|
||||
AppIcon(
|
||||
Icons.Default.CalendarMonth,
|
||||
contentDescription = "Toggle Calendar"
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { showSearch = !showSearch }) {
|
||||
Icon(Icons.Default.Search, contentDescription = null)
|
||||
AppIconButton(onClick = { showSearch = !showSearch }) {
|
||||
AppIcon(Icons.Default.Search, contentDescription = null)
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
@@ -271,10 +270,10 @@ fun ReadRecordScreen(
|
||||
onDismissRequest = {
|
||||
pendingDeleteAction = null
|
||||
},
|
||||
title = { Text("确认删除") },
|
||||
title = { AppText("确认删除") },
|
||||
text = {
|
||||
Column {
|
||||
Text("确定要删除这条记录吗?")
|
||||
AppText("确定要删除这条记录吗?")
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -289,10 +288,10 @@ fun ReadRecordScreen(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(checked = skipDeleteConfirmTemp, onCheckedChange = null)
|
||||
Text(
|
||||
AppText(
|
||||
text = "不再提示",
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = LegadoTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -311,30 +310,29 @@ fun ReadRecordScreen(
|
||||
TextButton(onClick = {
|
||||
pendingDeleteAction = null
|
||||
}) {
|
||||
Text("取消")
|
||||
AppText("取消")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showCalendar) {
|
||||
GlassModalBottomSheet(
|
||||
onDismissRequest = { showCalendar = false }
|
||||
) {
|
||||
HeatmapCalendarSection(
|
||||
dailyReadCounts = state.dailyReadCounts,
|
||||
dailyReadTimes = state.dailyReadTimes,
|
||||
selectedDate = state.selectedDate,
|
||||
onDateSelected = { date ->
|
||||
viewModel.setSelectedDate(date)
|
||||
showCalendar = false
|
||||
},
|
||||
onClearDate = {
|
||||
viewModel.setSelectedDate(null)
|
||||
showCalendar = false
|
||||
}
|
||||
)
|
||||
}
|
||||
AppModalBottomSheet(
|
||||
show = showCalendar,
|
||||
onDismissRequest = { showCalendar = false }
|
||||
) {
|
||||
HeatmapCalendarSection(
|
||||
dailyReadCounts = state.dailyReadCounts,
|
||||
dailyReadTimes = state.dailyReadTimes,
|
||||
selectedDate = state.selectedDate,
|
||||
onDateSelected = { date ->
|
||||
viewModel.setSelectedDate(date)
|
||||
showCalendar = false
|
||||
},
|
||||
onClearDate = {
|
||||
viewModel.setSelectedDate(null)
|
||||
showCalendar = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -346,10 +344,10 @@ fun ReadRecordScreen(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { mergeDialogData = null },
|
||||
title = { Text("合并阅读记录") },
|
||||
title = { AppText("合并阅读记录") },
|
||||
text = {
|
||||
Column {
|
||||
Text("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
|
||||
AppText("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
candidates.forEach { candidate ->
|
||||
@@ -367,10 +365,10 @@ fun ReadRecordScreen(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(checked = isChecked, onCheckedChange = null)
|
||||
Text(
|
||||
AppText(
|
||||
text = "$author(${formatDuring(candidate.readTime)})",
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = LegadoTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -386,12 +384,12 @@ fun ReadRecordScreen(
|
||||
mergeDialogData = null
|
||||
}
|
||||
) {
|
||||
Text("合并")
|
||||
AppText("合并")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { mergeDialogData = null }) {
|
||||
Text("取消")
|
||||
AppText("取消")
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -686,21 +684,21 @@ fun LatestReadItem(
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
AppText(
|
||||
text = record.bookName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = record.bookAuthor.ifBlank { "未知作者" },
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
AppText(
|
||||
modifier = Modifier
|
||||
.basicMarquee(
|
||||
iterations = Int.MAX_VALUE,
|
||||
@@ -716,7 +714,7 @@ fun LatestReadItem(
|
||||
append("${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
@@ -783,9 +781,9 @@ fun TimelineSessionItem(
|
||||
modifier = Modifier.width(48.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = endTimeText,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
style = LegadoTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
|
||||
@@ -795,23 +793,23 @@ fun TimelineSessionItem(
|
||||
Cover(coverPath)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(
|
||||
AppText(
|
||||
text = session.bookName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = session.bookAuthor.ifBlank { "未知作者" },
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = chapterTitle.orEmpty(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -847,21 +845,21 @@ fun ReadRecordItem(
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
AppText(
|
||||
text = detail.bookName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
maxLines = 1
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = detail.bookAuthor.ifBlank { "未知作者" },
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = "阅读时长: ${formatDuring(detail.readTime)}",
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
style = MaterialTheme.typography.labelSmall
|
||||
style = LegadoTheme.typography.labelSmall
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -872,28 +870,27 @@ fun DateHeader(
|
||||
date: String,
|
||||
dailyTotalTime: Long? = null
|
||||
) {
|
||||
val dateText = formatFriendlyDate(date)
|
||||
SectionHeader(
|
||||
CollapsibleHeader(
|
||||
showIcon = false,
|
||||
isCollapsed = false,
|
||||
onToggle = { },
|
||||
title = "",
|
||||
titleContent = {
|
||||
Column {
|
||||
Text(
|
||||
text = dateText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
val dateText = formatFriendlyDate(date)
|
||||
AppText(
|
||||
text = dateText,
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
dailyTotalTime?.let { total ->
|
||||
AppText(
|
||||
text = "已读 ${formatDuring(total)}",
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = LegadoTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
dailyTotalTime?.let { total ->
|
||||
Text(
|
||||
text = "已读 ${formatDuring(total)}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
detailContent = null,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -915,14 +912,12 @@ fun ReadingSummaryCard(
|
||||
|
||||
val totalDurationMinutes = totalTimeMillis / 60000
|
||||
|
||||
Card(
|
||||
GlassCard(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
)
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainer
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -934,28 +929,28 @@ fun ReadingSummaryCard(
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
style = LegadoTheme.typography.labelLarge,
|
||||
color = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text(
|
||||
AppText(
|
||||
text = "已读 ",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
style = LegadoTheme.typography.titleMedium
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = "$bookCount",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = LegadoTheme.typography.headlineMedium,
|
||||
color = LegadoTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = " 本书",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
style = LegadoTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
|
||||
@@ -965,10 +960,10 @@ fun ReadingSummaryCard(
|
||||
val minutes = totalDurationMinutes % 60
|
||||
val timeString = if (hours > 0) "${hours}小时${minutes}分钟" else "${minutes}分钟"
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = "共阅读 $timeString",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = LegadoTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
@@ -64,11 +63,9 @@ import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.data.entities.SearchContentHistory
|
||||
import io.legado.app.ui.widget.components.AnimatedText
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SearchBarSection
|
||||
import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
@@ -76,6 +73,9 @@ import io.legado.app.ui.widget.components.button.TopBarAnimatedActionButton
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@@ -140,12 +140,9 @@ fun SearchContentScreen(
|
||||
topBar = {
|
||||
Column {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
val title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
|
||||
"共 ${searchResults.size} 条结果"
|
||||
} else "搜索内容"
|
||||
AnimatedText(text = title)
|
||||
},
|
||||
title = if (searchQuery.isNotBlank() && searchResults.isNotEmpty()) {
|
||||
"共 ${searchResults.size} 条结果"
|
||||
} else "搜索内容",
|
||||
navigationIcon = { TopbarNavigationButton(onClick = onBack) },
|
||||
actions = {
|
||||
Row(
|
||||
@@ -191,7 +188,7 @@ fun SearchContentScreen(
|
||||
),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(if (isSearching) "停止搜索" else "跳转到当前章节")
|
||||
AppText(if (isSearching) "停止搜索" else "跳转到当前章节")
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
@@ -310,10 +307,10 @@ fun SearchHistoryList(
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = "搜索历史",
|
||||
style = MaterialTheme.typography.titleSmallEmphasized,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = LegadoTheme.typography.titleSmallEmphasized,
|
||||
color = LegadoTheme.colorScheme.primary,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
SmallAnimatedActionButton(
|
||||
@@ -342,7 +339,7 @@ fun SearchHistoryList(
|
||||
.clickable { onHistoryClick(item) }
|
||||
.animateItem(),
|
||||
headlineContent = {
|
||||
Text(
|
||||
AppText(
|
||||
text = item.query,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -378,7 +375,7 @@ fun SearchHistoryList(
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("清除搜索历史")
|
||||
AppText("清除搜索历史")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,7 +404,7 @@ fun SearchResultItem(
|
||||
Box(modifier = Modifier.padding(16.dp)) {
|
||||
|
||||
Column {
|
||||
Text(
|
||||
AppText(
|
||||
text = buildAnnotatedString {
|
||||
append(
|
||||
result.getTitleSpannable(
|
||||
@@ -415,14 +412,14 @@ fun SearchResultItem(
|
||||
)
|
||||
)
|
||||
},
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
style = LegadoTheme.typography.titleSmall
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
HorizontalDivider()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = buildAnnotatedString {
|
||||
append(
|
||||
result.getContentSpannable(
|
||||
@@ -432,7 +429,7 @@ fun SearchResultItem(
|
||||
)
|
||||
)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = LegadoTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ToggleFloatingActionButton
|
||||
import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
@@ -95,11 +94,11 @@ import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.help.book.isLocal
|
||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
||||
import io.legado.app.ui.replace.ReplaceEditRoute
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.CollapsibleHeader
|
||||
import io.legado.app.ui.widget.components.ActionItem
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SelectionBottomBar
|
||||
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
|
||||
import io.legado.app.ui.widget.components.bookmark.BookmarkItem
|
||||
@@ -109,7 +108,9 @@ import io.legado.app.ui.widget.components.divider.PillHeaderDivider
|
||||
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -312,33 +313,33 @@ fun TocScreen(
|
||||
when (pagerState.currentPage) {
|
||||
0 -> {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("使用替换规则") },
|
||||
text = "使用替换规则",
|
||||
trailingIcon = {
|
||||
Checkbox(checked = useReplace, onCheckedChange = null)
|
||||
},
|
||||
onClick = { viewModel.toggleUseReplace() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("显示字数") },
|
||||
text = "显示字数",
|
||||
trailingIcon = {
|
||||
Checkbox(checked = showWordCount, onCheckedChange = null)
|
||||
},
|
||||
onClick = { viewModel.toggleShowWordCount() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("反转目录") },
|
||||
text = "反转目录",
|
||||
onClick = { viewModel.reverseToc() }
|
||||
)
|
||||
PillDivider()
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("替换规则") },
|
||||
text = "替换规则",
|
||||
onClick = {
|
||||
onOpenReplaceRule(null)
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("新建替换规则") },
|
||||
text = "新建替换规则",
|
||||
onClick = {
|
||||
val scopes = mutableListOf<String>()
|
||||
book?.name?.let { scopes.add(it) }
|
||||
@@ -358,7 +359,7 @@ fun TocScreen(
|
||||
if (book?.isLocal == true) {
|
||||
PillHeaderDivider(title = "本地书籍选项")
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("本地书籍目录规则") },
|
||||
text = "本地书籍目录规则",
|
||||
onClick = {
|
||||
val intent =
|
||||
Intent(context, TxtTocRuleActivity::class.java).apply {
|
||||
@@ -369,7 +370,7 @@ fun TocScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("拆分超长章节") },
|
||||
text = "拆分超长章节",
|
||||
trailingIcon = {
|
||||
Checkbox(
|
||||
checked = viewModel.isSplitLongChapter,
|
||||
@@ -386,7 +387,7 @@ fun TocScreen(
|
||||
|
||||
else -> {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("导出书签为JSON") },
|
||||
text = "导出书签为JSON",
|
||||
onClick = {
|
||||
val dateFormat = SimpleDateFormat(
|
||||
"yyyyMMdd_HHmm",
|
||||
@@ -398,7 +399,7 @@ fun TocScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("导出书签为MarkDown") },
|
||||
text = "导出书签为MarkDown",
|
||||
onClick = {
|
||||
val dateFormat = SimpleDateFormat(
|
||||
"yyyyMMdd_HHmm",
|
||||
@@ -428,7 +429,7 @@ fun TocScreen(
|
||||
selected = pagerState.currentPage == 0,
|
||||
onClick = { scope.launch { pagerState.animateScrollToPage(0) } },
|
||||
text = {
|
||||
Text(
|
||||
AppText(
|
||||
text = "目录",
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
@@ -439,7 +440,7 @@ fun TocScreen(
|
||||
selected = pagerState.currentPage == 1,
|
||||
onClick = { scope.launch { pagerState.animateScrollToPage(1) } },
|
||||
text = {
|
||||
Text(
|
||||
AppText(
|
||||
text = "书签",
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
@@ -464,13 +465,13 @@ fun TocScreen(
|
||||
onDismissRequest = { showVolumeMenu = false }
|
||||
) {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("展开所有卷") },
|
||||
text = "展开所有卷",
|
||||
onClick = {
|
||||
viewModel.expandAllVolumes(); showVolumeMenu = false
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("收起所有卷") },
|
||||
text = "收起所有卷",
|
||||
onClick = {
|
||||
viewModel.collapseAllVolumes(); showVolumeMenu = false
|
||||
}
|
||||
@@ -482,7 +483,7 @@ fun TocScreen(
|
||||
PillHeaderDivider(title = "快速跳转")
|
||||
volumeItems.forEach { uiItem ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(uiItem.title) },
|
||||
text = uiItem.title,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
val targetIndex =
|
||||
@@ -541,7 +542,7 @@ fun TocScreen(
|
||||
fabMenuExpanded = false
|
||||
},
|
||||
icon = { Icon(icon, contentDescription = null) },
|
||||
text = { Text(text = label) }
|
||||
text = { AppText(text = label) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -593,20 +594,19 @@ fun TocScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (editingBookmark != null) {
|
||||
BookmarkEditSheet(
|
||||
bookmark = editingBookmark!!,
|
||||
onDismiss = { editingBookmark = null },
|
||||
onSave = { updatedBookmark ->
|
||||
viewModel.updateBookmark(updatedBookmark)
|
||||
editingBookmark = null
|
||||
},
|
||||
onDelete = { bookmarkToDelete ->
|
||||
viewModel.deleteBookmark(bookmarkToDelete)
|
||||
editingBookmark = null
|
||||
}
|
||||
)
|
||||
}
|
||||
BookmarkEditSheet(
|
||||
show = editingBookmark != null,
|
||||
bookmark = editingBookmark!!,
|
||||
onDismiss = { editingBookmark = null },
|
||||
onSave = { updatedBookmark ->
|
||||
viewModel.updateBookmark(updatedBookmark)
|
||||
editingBookmark = null
|
||||
},
|
||||
onDelete = { bookmarkToDelete ->
|
||||
viewModel.deleteBookmark(bookmarkToDelete)
|
||||
editingBookmark = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,9 +729,9 @@ fun ChapterItem(
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = item.title,
|
||||
style = MaterialTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
|
||||
style = LegadoTheme.typography.bodyMediumEmphasized.copy(fontWeight = FontWeight.Medium),
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -739,9 +739,9 @@ fun ChapterItem(
|
||||
}
|
||||
|
||||
if (!item.tag.isNullOrEmpty()) {
|
||||
Text(
|
||||
AppText(
|
||||
text = item.tag,
|
||||
style = MaterialTheme.typography.labelSmallEmphasized,
|
||||
style = LegadoTheme.typography.labelSmallEmphasized,
|
||||
color = detailColor.copy(alpha = 0.8f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -910,11 +910,11 @@ private fun StatusIcon(
|
||||
color = Color.Transparent
|
||||
) {
|
||||
if (wordCount != null) {
|
||||
Text(
|
||||
AppText(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = wordCount,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = 9.sp),
|
||||
style = LegadoTheme.typography.labelSmall.copy(fontSize = 9.sp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -51,7 +48,6 @@ import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
|
||||
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
||||
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
@@ -59,6 +55,7 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.rules.RuleEditFields
|
||||
import io.legado.app.ui.widget.components.rules.RuleEditSheet
|
||||
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
|
||||
@@ -154,54 +151,49 @@ fun TxtRuleScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showExportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
title = stringResource(R.string.export),
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportDoc.launch("exportDictRule.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showExportSheet,
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
title = stringResource(R.string.export),
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportDoc.launch("exportDictRule.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
|
||||
if (showImportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_txt_toc_rule),
|
||||
onSelectSysFile = { types ->
|
||||
importDoc.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showUrlInput = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
}
|
||||
|
||||
(importState as? BaseImportUiState.Success<TxtTocRule>)?.let { state ->
|
||||
BatchImportDialog(
|
||||
title = "导入词典规则",
|
||||
importState = state,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
itemContent = { rule, _ ->
|
||||
Column {
|
||||
Text(rule.name, style = MaterialTheme.typography.titleMedium)
|
||||
Text(rule.rule, style = MaterialTheme.typography.bodySmall, maxLines = 1)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showImportSheet,
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_txt_toc_rule),
|
||||
onSelectSysFile = { types ->
|
||||
importDoc.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showUrlInput = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
|
||||
BatchImportDialog(
|
||||
title = "导入词典规则",
|
||||
importState = importState,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
itemTitle = { rule -> rule.name },
|
||||
itemSubtitle = { rule ->
|
||||
rule.rule.takeIf { it.isNotBlank() }
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(reorderableState.isAnyItemDragging) {
|
||||
if (!reorderableState.isAnyItemDragging) {
|
||||
@@ -212,16 +204,16 @@ fun TxtRuleScreen(
|
||||
showDeleteRuleDialog?.let { rule ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteRuleDialog = null },
|
||||
title = { Text(stringResource(R.string.delete)) },
|
||||
text = { Text(stringResource(R.string.del_msg)) },
|
||||
title = { AppText(stringResource(R.string.delete)) },
|
||||
text = { AppText(stringResource(R.string.del_msg)) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.delete(rule); showDeleteRuleDialog = null
|
||||
}) { Text(stringResource(R.string.ok)) }
|
||||
}) { AppText(stringResource(R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteRuleDialog = null }) {
|
||||
Text(
|
||||
AppText(
|
||||
stringResource(R.string.cancel)
|
||||
)
|
||||
}
|
||||
@@ -229,48 +221,47 @@ fun TxtRuleScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showEditSheet) {
|
||||
RuleEditSheet(
|
||||
rule = editingRule,
|
||||
title = stringResource(R.string.txt_toc_rule),
|
||||
label1 = stringResource(R.string.regex),
|
||||
label2 = stringResource(R.string.example),
|
||||
onDismissRequest = {
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onSave = { updatedRule ->
|
||||
//TODO:我很想把他改为自增主键,但为了兼容性日后再说
|
||||
if (editingRule == null) {
|
||||
viewModel.insert(updatedRule)
|
||||
} else {
|
||||
viewModel.update(updatedRule)
|
||||
}
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onCopy = { viewModel.copyRule(it) },
|
||||
onPaste = { viewModel.pasteRule() },
|
||||
toFields = { r ->
|
||||
RuleEditFields(
|
||||
name = r?.name ?: "",
|
||||
rule1 = r?.rule ?: "",
|
||||
rule2 = r?.example ?: ""
|
||||
)
|
||||
},
|
||||
fromFields = { fields, old ->
|
||||
old?.copy(
|
||||
name = fields.name,
|
||||
rule = fields.rule1,
|
||||
example = fields.rule2
|
||||
) ?: TxtTocRule(
|
||||
name = fields.name,
|
||||
rule = fields.rule1,
|
||||
example = fields.rule2
|
||||
)
|
||||
RuleEditSheet(
|
||||
show = showEditSheet,
|
||||
rule = editingRule,
|
||||
title = stringResource(R.string.txt_toc_rule),
|
||||
label1 = stringResource(R.string.regex),
|
||||
label2 = stringResource(R.string.example),
|
||||
onDismissRequest = {
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onSave = { updatedRule ->
|
||||
//TODO:我很想把他改为自增主键,但为了兼容性日后再说
|
||||
if (editingRule == null) {
|
||||
viewModel.insert(updatedRule)
|
||||
} else {
|
||||
viewModel.update(updatedRule)
|
||||
}
|
||||
)
|
||||
}
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onCopy = { viewModel.copyRule(it) },
|
||||
onPaste = { viewModel.pasteRule() },
|
||||
toFields = { r ->
|
||||
RuleEditFields(
|
||||
name = r?.name ?: "",
|
||||
rule1 = r?.rule ?: "",
|
||||
rule2 = r?.example ?: ""
|
||||
)
|
||||
},
|
||||
fromFields = { fields, old ->
|
||||
old?.copy(
|
||||
name = fields.name,
|
||||
rule = fields.rule1,
|
||||
example = fields.rule2
|
||||
) ?: TxtTocRule(
|
||||
name = fields.name,
|
||||
rule = fields.rule1,
|
||||
example = fields.rule2
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
RuleListScaffold(
|
||||
title = if (isPickMode) "选择目录规则" else "目录规则",
|
||||
@@ -312,7 +303,7 @@ fun TxtRuleScreen(
|
||||
snackbarHostState = snackbarHostState,
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_str)) },
|
||||
text = stringResource(R.string.import_str),
|
||||
onClick = { showImportSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -14,11 +13,11 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -36,7 +35,7 @@ fun ConfigNavScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(stringResource(R.string.setting)) },
|
||||
title = stringResource(R.string.setting),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -42,14 +41,15 @@ import io.legado.app.help.storage.Restore
|
||||
import io.legado.app.lib.permission.Permissions
|
||||
import io.legado.app.lib.permission.PermissionsCompat
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.InputSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.utils.isContentScheme
|
||||
import io.legado.app.utils.takePersistablePermissionSafely
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -166,9 +166,7 @@ fun BackupConfigScreen(
|
||||
},
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.backup_restore))
|
||||
},
|
||||
title = stringResource(R.string.backup_restore),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
@@ -387,19 +385,19 @@ fun BackupConfigScreen(
|
||||
if (showWebDavAuthDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showWebDavAuthDialog = false },
|
||||
title = { Text(stringResource(R.string.web_dav_account)) },
|
||||
title = { AppText(stringResource(R.string.web_dav_account)) },
|
||||
text = {
|
||||
Column {
|
||||
TextField(
|
||||
value = tempAccount,
|
||||
onValueChange = { tempAccount = it },
|
||||
label = { Text("账号") }
|
||||
label = { AppText("账号") }
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
TextField(
|
||||
value = tempPassword,
|
||||
onValueChange = { tempPassword = it },
|
||||
label = { Text("密码") },
|
||||
label = { AppText("密码") },
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
trailingIcon = {
|
||||
@@ -432,12 +430,12 @@ fun BackupConfigScreen(
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showWebDavAuthDialog = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -455,36 +453,35 @@ fun BackupConfigScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showBackupFilePicker) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showBackupFilePicker = false },
|
||||
onSelectSysDir = {
|
||||
showBackupFilePicker = false
|
||||
try {
|
||||
selectBackupPathLauncher.launch(null)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showBackupFilePicker,
|
||||
onDismissRequest = { showBackupFilePicker = false },
|
||||
onSelectSysDir = {
|
||||
showBackupFilePicker = false
|
||||
try {
|
||||
selectBackupPathLauncher.launch(null)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (showRestoreFilePicker) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showRestoreFilePicker = false },
|
||||
onSelectSysDir = {
|
||||
showRestoreFilePicker = false
|
||||
try {
|
||||
backupAndSelectLauncher.launch(null)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
FilePickerSheet(
|
||||
show = showRestoreFilePicker,
|
||||
onDismissRequest = { showRestoreFilePicker = false },
|
||||
onSelectSysDir = {
|
||||
showRestoreFilePicker = false
|
||||
try {
|
||||
backupAndSelectLauncher.launch(null)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (showRestoreSheet && backupNames.isNotEmpty()) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRestoreSheet = false },
|
||||
title = { Text(stringResource(R.string.select_restore_file)) },
|
||||
title = { AppText(stringResource(R.string.select_restore_file)) },
|
||||
text = {
|
||||
Column {
|
||||
backupNames.forEach { name ->
|
||||
@@ -508,7 +505,7 @@ fun BackupConfigScreen(
|
||||
}
|
||||
)
|
||||
}) {
|
||||
Text(name)
|
||||
AppText(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -516,7 +513,7 @@ fun BackupConfigScreen(
|
||||
confirmButton = {},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showRestoreSheet = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -535,7 +532,7 @@ fun BackupConfigScreen(
|
||||
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
|
||||
showBackupIgnoreDialog = false
|
||||
},
|
||||
title = { Text(stringResource(R.string.restore_ignore)) },
|
||||
title = { AppText(stringResource(R.string.restore_ignore)) },
|
||||
text = {
|
||||
Column {
|
||||
io.legado.app.help.storage.BackupConfig.ignoreTitle.forEachIndexed { index, title ->
|
||||
@@ -546,7 +543,7 @@ fun BackupConfigScreen(
|
||||
] = checkedItems[index]
|
||||
}) {
|
||||
val isChecked = checkedItems[index]
|
||||
Text(if (isChecked) "✓ $title" else title)
|
||||
AppText(if (isChecked) "✓ $title" else title)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +553,7 @@ fun BackupConfigScreen(
|
||||
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
|
||||
showBackupIgnoreDialog = false
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
@@ -564,7 +561,7 @@ fun BackupConfigScreen(
|
||||
io.legado.app.help.storage.BackupConfig.saveIgnoreConfig()
|
||||
showBackupIgnoreDialog = false
|
||||
}) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -573,7 +570,7 @@ fun BackupConfigScreen(
|
||||
if (showLoadingDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text(loadingText) },
|
||||
title = { AppText(loadingText) },
|
||||
confirmButton = {},
|
||||
dismissButton = {}
|
||||
)
|
||||
@@ -589,16 +586,16 @@ fun ConfirmDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(title) },
|
||||
text = { Text(text) },
|
||||
title = { AppText(title) },
|
||||
text = { AppText(text) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -27,14 +26,14 @@ import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.PreferKey
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -52,7 +51,7 @@ fun CoverConfigScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(stringResource(R.string.cover_config)) },
|
||||
title = stringResource(R.string.cover_config),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
@@ -247,17 +246,17 @@ fun CoverConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showCoverRuleSheet) {
|
||||
CoverRuleConfigSheet(onDismissRequest = { showCoverRuleSheet = false })
|
||||
}
|
||||
CoverRuleConfigSheet(
|
||||
show = showCoverRuleSheet,
|
||||
onDismissRequest = { showCoverRuleSheet = false }
|
||||
)
|
||||
|
||||
manageKey?.let { key ->
|
||||
CoverManageSheet(
|
||||
preferenceKey = key,
|
||||
onDismissRequest = { manageKey = null },
|
||||
viewModel = viewModel
|
||||
)
|
||||
}
|
||||
CoverManageSheet(
|
||||
show = manageKey != null,
|
||||
preferenceKey = manageKey!!,
|
||||
onDismissRequest = { manageKey = null },
|
||||
viewModel = viewModel
|
||||
)
|
||||
|
||||
showColorPickerByField?.let { field ->
|
||||
val initialColor = when (field) {
|
||||
@@ -269,6 +268,7 @@ fun CoverConfigScreen(
|
||||
}
|
||||
|
||||
ColorPickerSheet(
|
||||
show = showColorPickerByField != null,
|
||||
initialColor = initialColor,
|
||||
onDismissRequest = { showColorPickerByField = null },
|
||||
onColorSelected = { color ->
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -36,12 +35,15 @@ import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.PreferKey
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CoverManageSheet(
|
||||
show: Boolean,
|
||||
preferenceKey: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: CoverConfigViewModel
|
||||
@@ -61,7 +63,8 @@ fun CoverManageSheet(
|
||||
}
|
||||
val coverList = currentCovers.split(",").filter { it.isNotBlank() }
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Column(
|
||||
@@ -69,9 +72,9 @@ fun CoverManageSheet(
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.default_cover),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
@@ -141,14 +144,13 @@ fun CoverManageSheet(
|
||||
}
|
||||
}
|
||||
|
||||
if (showFilePicker) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showFilePicker = false },
|
||||
onSelectSysFiles = {
|
||||
selectImages.launch("image/*")
|
||||
showFilePicker = false
|
||||
},
|
||||
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showFilePicker,
|
||||
onDismissRequest = { showFilePicker = false },
|
||||
onSelectSysFiles = {
|
||||
selectImages.launch("image/*")
|
||||
showFilePicker = false
|
||||
},
|
||||
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -27,8 +26,10 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.model.BookCover
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.InputSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -37,6 +38,7 @@ import splitties.init.appCtx
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CoverRuleConfigSheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
var enable by remember { mutableStateOf(false) }
|
||||
@@ -52,7 +54,8 @@ fun CoverRuleConfigSheet(
|
||||
coverRule = rule.coverRule
|
||||
}
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Column(
|
||||
@@ -61,9 +64,9 @@ fun CoverRuleConfigSheet(
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.cover_rule),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
style = LegadoTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
@@ -75,7 +78,7 @@ fun CoverRuleConfigSheet(
|
||||
checked = enable,
|
||||
onCheckedChange = { enable = it }
|
||||
)
|
||||
Text(text = stringResource(R.string.enable))
|
||||
AppText(text = stringResource(R.string.enable))
|
||||
}
|
||||
|
||||
InputSettingItem(
|
||||
@@ -100,13 +103,16 @@ fun CoverRuleConfigSheet(
|
||||
BookCover.delCoverRule()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(stringResource(R.string.delete), color = MaterialTheme.colorScheme.error)
|
||||
AppText(
|
||||
stringResource(R.string.delete),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
OutlinedButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.padding(horizontal = 4.dp))
|
||||
@@ -121,7 +127,7 @@ fun CoverRuleConfigSheet(
|
||||
onDismissRequest()
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -18,21 +17,25 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
|
||||
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CheckSourceBottomSheet(
|
||||
show: Boolean,
|
||||
viewModel: OtherConfigViewModel = koinViewModel(),
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismiss
|
||||
) {
|
||||
Column(
|
||||
@@ -41,9 +44,9 @@ fun CheckSourceBottomSheet(
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 32.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.check_source_config),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
style = LegadoTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
|
||||
@@ -131,7 +134,7 @@ fun CheckSourceBottomSheet(
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
Button(onClick = {
|
||||
if (viewModel.saveCheckSourceConfig()) {
|
||||
@@ -140,7 +143,7 @@ fun CheckSourceBottomSheet(
|
||||
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-18
@@ -24,7 +24,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -41,8 +40,10 @@ import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.DirectLinkUpload
|
||||
import io.legado.app.lib.dialogs.selector
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.GSON
|
||||
import io.legado.app.utils.fromJsonObject
|
||||
import io.legado.app.utils.getClipText
|
||||
@@ -52,6 +53,7 @@ import io.legado.app.utils.toastOnUi
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DirectLinkUploadBottomSheet(
|
||||
show: Boolean,
|
||||
viewModel: OtherConfigViewModel,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
@@ -63,7 +65,8 @@ fun DirectLinkUploadBottomSheet(
|
||||
viewModel.initDirectLinkRule()
|
||||
}
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismiss
|
||||
) {
|
||||
Column(
|
||||
@@ -75,9 +78,9 @@ fun DirectLinkUploadBottomSheet(
|
||||
) {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.direct_link_upload_config),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
style = LegadoTheme.typography.titleLarge
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
@@ -89,7 +92,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("导入默认") },
|
||||
text = { AppText("导入默认") },
|
||||
leadingIcon = { Icon(Icons.Default.Download, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
@@ -99,7 +102,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.copy_rule)) },
|
||||
text = { AppText(stringResource(R.string.copy_rule)) },
|
||||
leadingIcon = { Icon(Icons.Default.ContentCopy, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
@@ -113,7 +116,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.paste_rule)) },
|
||||
text = { AppText(stringResource(R.string.paste_rule)) },
|
||||
leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
@@ -141,7 +144,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
OutlinedTextField(
|
||||
value = viewModel.uploadUrl,
|
||||
onValueChange = { viewModel.uploadUrl = it },
|
||||
label = { Text(stringResource(R.string.upload_url)) },
|
||||
label = { AppText(stringResource(R.string.upload_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -150,7 +153,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
OutlinedTextField(
|
||||
value = viewModel.downloadUrlRule,
|
||||
onValueChange = { viewModel.downloadUrlRule = it },
|
||||
label = { Text(stringResource(R.string.download_url_rule)) },
|
||||
label = { AppText(stringResource(R.string.download_url_rule)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -159,7 +162,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
OutlinedTextField(
|
||||
value = viewModel.summary,
|
||||
onValueChange = { viewModel.summary = it },
|
||||
label = { Text(stringResource(R.string.summary)) },
|
||||
label = { AppText(stringResource(R.string.summary)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -178,11 +181,11 @@ fun DirectLinkUploadBottomSheet(
|
||||
TextButton(onClick = {
|
||||
viewModel.testRule { result -> showTestResult = result }
|
||||
}) {
|
||||
Text("测试")
|
||||
AppText("测试")
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
OutlinedButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
Button(onClick = {
|
||||
if (viewModel.saveDirectLinkRule()) {
|
||||
@@ -191,7 +194,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
context.toastOnUi("请填写完整信息")
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,15 +203,15 @@ fun DirectLinkUploadBottomSheet(
|
||||
showTestResult?.let { result ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showTestResult = null },
|
||||
title = { Text("Result") },
|
||||
text = { Text(result) },
|
||||
title = { AppText("Result") },
|
||||
text = { AppText(result) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showTestResult = null
|
||||
}) { Text(stringResource(R.string.ok)) }
|
||||
}) { AppText(stringResource(R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { context.sendToClip(result) }) { Text(stringResource(R.string.copy_text)) }
|
||||
TextButton(onClick = { context.sendToClip(result) }) { AppText(stringResource(R.string.copy_text)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -30,8 +29,6 @@ import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.service.WebService
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
@@ -40,6 +37,9 @@ import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.InputSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.utils.restart
|
||||
import io.legado.app.utils.takePersistablePermissionSafely
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -83,9 +83,7 @@ fun OtherConfigScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.other_setting))
|
||||
},
|
||||
title = stringResource(R.string.other_setting),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
@@ -200,7 +198,10 @@ fun OtherConfigScreen(
|
||||
|
||||
SliderSettingItem(
|
||||
title = stringResource(R.string.bitmap_cache_size),
|
||||
description = stringResource(R.string.bitmap_cache_size_summary),
|
||||
description = stringResource(
|
||||
R.string.bitmap_cache_size_summary,
|
||||
OtherConfig.bitmapCacheSize
|
||||
),
|
||||
value = OtherConfig.bitmapCacheSize.toFloat(),
|
||||
defaultValue = 32f,
|
||||
valueRange = 1f..2047f,
|
||||
@@ -211,7 +212,10 @@ fun OtherConfigScreen(
|
||||
|
||||
SliderSettingItem(
|
||||
title = stringResource(R.string.image_retain_number),
|
||||
description = stringResource(R.string.image_retain_number_summary),
|
||||
description = stringResource(
|
||||
R.string.image_retain_number_summary,
|
||||
OtherConfig.imageRetainNum
|
||||
),
|
||||
value = OtherConfig.imageRetainNum.toFloat(),
|
||||
defaultValue = 10f,
|
||||
valueRange = 0f..100f,
|
||||
@@ -220,7 +224,10 @@ fun OtherConfigScreen(
|
||||
|
||||
SliderSettingItem(
|
||||
title = stringResource(R.string.pre_download),
|
||||
description = stringResource(R.string.pre_download_s),
|
||||
description = stringResource(
|
||||
R.string.pre_download_s,
|
||||
OtherConfig.preDownloadNum
|
||||
),
|
||||
value = OtherConfig.preDownloadNum.toFloat(),
|
||||
defaultValue = 10f,
|
||||
valueRange = 0f..100f,
|
||||
@@ -391,33 +398,30 @@ fun OtherConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showFilePicker) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showFilePicker = false },
|
||||
onSelectSysDir = {
|
||||
showFilePicker = false
|
||||
try {
|
||||
selectDocTree.launch(null)
|
||||
} catch (e: Exception) {
|
||||
FilePickerSheet(
|
||||
show = showFilePicker,
|
||||
onDismissRequest = { showFilePicker = false },
|
||||
onSelectSysDir = {
|
||||
showFilePicker = false
|
||||
try {
|
||||
selectDocTree.launch(null)
|
||||
} catch (e: Exception) {
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (showCheckSourceSheet) {
|
||||
CheckSourceBottomSheet(
|
||||
viewModel = viewModel,
|
||||
onDismiss = { showCheckSourceSheet = false }
|
||||
)
|
||||
}
|
||||
CheckSourceBottomSheet(
|
||||
show = showCheckSourceSheet,
|
||||
viewModel = viewModel,
|
||||
onDismiss = { showCheckSourceSheet = false }
|
||||
)
|
||||
|
||||
if (showDirectLinkUploadSheet) {
|
||||
DirectLinkUploadBottomSheet(
|
||||
viewModel = viewModel,
|
||||
onDismiss = { showDirectLinkUploadSheet = false }
|
||||
)
|
||||
}
|
||||
DirectLinkUploadBottomSheet(
|
||||
show = showDirectLinkUploadSheet,
|
||||
viewModel = viewModel,
|
||||
onDismiss = { showDirectLinkUploadSheet = false }
|
||||
)
|
||||
|
||||
if (showClearCacheDialog) {
|
||||
ConfirmDialog(
|
||||
@@ -458,23 +462,23 @@ fun OtherConfigScreen(
|
||||
if (showPasswordDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showPasswordDialog = false },
|
||||
title = { Text(stringResource(R.string.set_local_password)) },
|
||||
title = { AppText(stringResource(R.string.set_local_password)) },
|
||||
text = {
|
||||
TextField(
|
||||
value = tempPassword,
|
||||
onValueChange = { tempPassword = it },
|
||||
label = { Text("Password") }
|
||||
label = { AppText("Password") }
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
viewModel.setLocalPassword(tempPassword)
|
||||
showPasswordDialog = false
|
||||
}) { Text(stringResource(R.string.ok)) }
|
||||
}) { AppText(stringResource(R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showPasswordDialog = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -492,16 +496,16 @@ fun ConfirmDialog(
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(title) },
|
||||
text = { Text(text) },
|
||||
title = { AppText(title) },
|
||||
text = { AppText(text) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -23,33 +22,36 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PageKeySheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
var prevKeys by remember { mutableStateOf(ReadConfig.prevKeys) }
|
||||
var nextKeys by remember { mutableStateOf(ReadConfig.nextKeys) }
|
||||
|
||||
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
|
||||
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
|
||||
Column(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.custom_page_key),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
style = LegadoTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Companion.Bold
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = prevKeys,
|
||||
onValueChange = { prevKeys = it },
|
||||
label = { Text(stringResource(R.string.prev_page_key)) },
|
||||
label = { AppText(stringResource(R.string.prev_page_key)) },
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.onPreviewKeyEvent { event ->
|
||||
@@ -72,7 +74,7 @@ fun PageKeySheet(
|
||||
OutlinedTextField(
|
||||
value = nextKeys,
|
||||
onValueChange = { nextKeys = it },
|
||||
label = { Text(stringResource(R.string.next_page_key)) },
|
||||
label = { AppText(stringResource(R.string.next_page_key)) },
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.onPreviewKeyEvent { event ->
|
||||
@@ -92,9 +94,9 @@ fun PageKeySheet(
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.page_key_set_help),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
@@ -109,7 +111,7 @@ fun PageKeySheet(
|
||||
},
|
||||
modifier = Modifier.Companion.weight(1f)
|
||||
) {
|
||||
Text(stringResource(R.string.reset))
|
||||
AppText(stringResource(R.string.reset))
|
||||
}
|
||||
|
||||
Button(
|
||||
@@ -120,7 +122,7 @@ fun PageKeySheet(
|
||||
},
|
||||
modifier = Modifier.Companion.weight(1f)
|
||||
) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -20,14 +19,14 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@@ -45,9 +44,7 @@ fun ReadConfigScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.read_config))
|
||||
},
|
||||
title = stringResource(R.string.read_config),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
@@ -323,7 +320,8 @@ fun ReadConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showPageKeySheet) {
|
||||
PageKeySheet(onDismissRequest = { showPageKeySheet = false })
|
||||
}
|
||||
PageKeySheet(
|
||||
show = showPageKeySheet,
|
||||
onDismissRequest = { showPageKeySheet = false }
|
||||
)
|
||||
}
|
||||
|
||||
+17
-15
@@ -17,7 +17,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -31,14 +30,17 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BackgroundImageManageSheet(
|
||||
show: Boolean,
|
||||
isDarkTheme: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: ThemeConfigViewModel = koinViewModel()
|
||||
@@ -64,7 +66,8 @@ fun BackgroundImageManageSheet(
|
||||
ThemeConfig.bgImageLight
|
||||
}
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Column(
|
||||
@@ -72,9 +75,9 @@ fun BackgroundImageManageSheet(
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.background_image),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
@@ -139,14 +142,13 @@ fun BackgroundImageManageSheet(
|
||||
}
|
||||
}
|
||||
|
||||
if (showFilePicker) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showFilePicker = false },
|
||||
onSelectSysFile = {
|
||||
selectImage.launch("image/*")
|
||||
showFilePicker = false
|
||||
},
|
||||
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showFilePicker,
|
||||
onDismissRequest = { showFilePicker = false },
|
||||
onSelectSysFile = {
|
||||
selectImage.launch("image/*")
|
||||
showFilePicker = false
|
||||
},
|
||||
allowExtensions = arrayOf("jpg", "jpeg", "png", "webp")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -39,13 +38,16 @@ import io.legado.app.ui.main.Launcher4
|
||||
import io.legado.app.ui.main.Launcher5
|
||||
import io.legado.app.ui.main.Launcher6
|
||||
import io.legado.app.ui.main.LauncherW
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.getCompatDrawable
|
||||
import splitties.init.appCtx
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LauncherIconPickerSheet(
|
||||
show: Boolean,
|
||||
selectedValue: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onValueChange: (String) -> Unit
|
||||
@@ -53,7 +55,8 @@ fun LauncherIconPickerSheet(
|
||||
val context = LocalContext.current
|
||||
val icons = LauncherIcons.list
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Column(
|
||||
@@ -61,9 +64,9 @@ fun LauncherIconPickerSheet(
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.change_icon),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
|
||||
@@ -15,12 +15,15 @@ object ThemeConfig {
|
||||
|
||||
var enableBlur by prefDelegate(PreferKey.enableBlur, false)
|
||||
|
||||
var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, true)
|
||||
var enableProgressiveBlur by prefDelegate(PreferKey.enableProgressiveBlur, false)
|
||||
|
||||
var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true)
|
||||
|
||||
var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot")
|
||||
|
||||
//m3 or miuix
|
||||
var composeEngine by prefDelegate(PreferKey.composeEngine, "material")
|
||||
|
||||
var materialVersion by prefDelegate(PreferKey.materialVersion, "material3")
|
||||
|
||||
var appTheme by prefDelegate(PreferKey.appTheme, "0")
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -78,11 +79,10 @@ import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.OldThemeConfig
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.lib.theme.primaryColor
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeManager
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
|
||||
@@ -90,10 +90,17 @@ import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.restart
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
|
||||
import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Text as MiuixText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -121,7 +128,7 @@ fun ThemeConfigScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(stringResource(R.string.theme_setting)) },
|
||||
title = stringResource(R.string.theme_setting),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBackClick)
|
||||
@@ -136,23 +143,29 @@ fun ThemeConfigScreen(
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
val composeEngine = ThemeConfig.composeEngine
|
||||
val isMiuixEngine = remember(composeEngine) {
|
||||
ThemeResolver.isMiuixEngine(composeEngine)
|
||||
}
|
||||
val isDarkTheme = when (selectedThemeMode) {
|
||||
"1" -> false
|
||||
"2" -> true
|
||||
else -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
ThemeCard(
|
||||
context = context,
|
||||
value = selectedTheme,
|
||||
isDark = isDarkTheme,
|
||||
isAmoled = ThemeConfig.isPureBlack,
|
||||
paletteStyle = ThemeConfig.paletteStyle
|
||||
)
|
||||
if (!isMiuixEngine) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
ThemeCard(
|
||||
context = context,
|
||||
value = selectedTheme,
|
||||
isDark = isDarkTheme,
|
||||
isAmoled = ThemeConfig.isPureBlack,
|
||||
paletteStyle = ThemeConfig.paletteStyle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
@@ -163,47 +176,77 @@ fun ThemeConfigScreen(
|
||||
themeItems.zip(themeValues).toList()
|
||||
}
|
||||
|
||||
if (isMiuixEngine) {
|
||||
MiuixCard(
|
||||
cornerRadius = 16.dp,
|
||||
insideMargin = PaddingValues(16.dp),
|
||||
colors = MiuixCardDefaults.defaultColors(
|
||||
color = MiuixTheme.colorScheme.primaryVariant,
|
||||
contentColor = MiuixTheme.colorScheme.onPrimary
|
||||
)
|
||||
) {
|
||||
MiuixText("Miuix 目前为测试主题,且不对基于View的界面生效!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SplicedColumnGroup(title = stringResource(R.string.theme)) {
|
||||
ThemeModeSelector(
|
||||
selectedMode = selectedThemeMode,
|
||||
onModeSelected = { mode ->
|
||||
selectedThemeMode = mode
|
||||
ThemeConfig.themeMode = mode
|
||||
OldThemeConfig.applyDayNight(context)
|
||||
}
|
||||
)
|
||||
if (isMiuixEngine) {
|
||||
DropdownListSettingItem(
|
||||
title = stringResource(R.string.theme_mode),
|
||||
selectedValue = selectedThemeMode,
|
||||
displayEntries = stringArrayResource(R.array.theme_mode),
|
||||
entryValues = stringArrayResource(R.array.theme_mode_v),
|
||||
onValueChange = { mode ->
|
||||
selectedThemeMode = mode
|
||||
ThemeConfig.themeMode = mode
|
||||
OldThemeConfig.applyDayNight(context)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
ThemeModeSelector(
|
||||
selectedMode = selectedThemeMode,
|
||||
onModeSelected = { mode ->
|
||||
selectedThemeMode = mode
|
||||
ThemeConfig.themeMode = mode
|
||||
OldThemeConfig.applyDayNight(context)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
if (!isMiuixEngine) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ThemeColorSelector(
|
||||
context = context,
|
||||
themes = themes,
|
||||
selectedTheme = selectedTheme,
|
||||
isDark = isDarkTheme,
|
||||
isAmoled = ThemeConfig.isPureBlack,
|
||||
paletteStyle = ThemeConfig.paletteStyle,
|
||||
onThemeSelected = { theme ->
|
||||
if (theme == "13") {
|
||||
val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
|
||||
val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
|
||||
if (!hasLightBg || !hasDarkBg) {
|
||||
context.toastOnUi(R.string.transparent_theme_alarm)
|
||||
return@ThemeColorSelector
|
||||
ThemeColorSelector(
|
||||
context = context,
|
||||
themes = themes,
|
||||
selectedTheme = selectedTheme,
|
||||
isDark = isDarkTheme,
|
||||
isAmoled = ThemeConfig.isPureBlack,
|
||||
paletteStyle = ThemeConfig.paletteStyle,
|
||||
onThemeSelected = { theme ->
|
||||
if (theme == "13") {
|
||||
val hasLightBg = !ThemeConfig.bgImageLight.isNullOrEmpty()
|
||||
val hasDarkBg = !ThemeConfig.bgImageDark.isNullOrEmpty()
|
||||
if (!hasLightBg || !hasDarkBg) {
|
||||
context.toastOnUi(R.string.transparent_theme_alarm)
|
||||
return@ThemeColorSelector
|
||||
} else {
|
||||
AppConfig.containerOpacity = 0
|
||||
}
|
||||
}
|
||||
val oldTheme = selectedTheme
|
||||
selectedTheme = theme
|
||||
ThemeConfig.appTheme = theme
|
||||
val isDynamicSwitch = (oldTheme == "12" || theme == "12")
|
||||
if (isDynamicSwitch) {
|
||||
showRestartDialog = true
|
||||
} else {
|
||||
AppConfig.containerOpacity = 0
|
||||
postEvent(EventBus.RECREATE, "")
|
||||
}
|
||||
}
|
||||
val oldTheme = selectedTheme
|
||||
selectedTheme = theme
|
||||
ThemeConfig.appTheme = theme
|
||||
val isDynamicSwitch = (oldTheme == "12" || theme == "12")
|
||||
if (isDynamicSwitch) {
|
||||
showRestartDialog = true
|
||||
} else {
|
||||
postEvent(EventBus.RECREATE, "")
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SplicedColumnGroup {
|
||||
@@ -226,13 +269,22 @@ fun ThemeConfigScreen(
|
||||
context.toastOnUi(R.string.restart_to_apply)
|
||||
}
|
||||
)
|
||||
DropdownListSettingItem(
|
||||
title = stringResource(R.string.compose_engine),
|
||||
selectedValue = ThemeConfig.composeEngine,
|
||||
displayEntries = stringArrayResource(R.array.composeEngine),
|
||||
entryValues = stringArrayResource(R.array.composeEngine_value),
|
||||
onValueChange = {
|
||||
ThemeConfig.composeEngine = it
|
||||
}
|
||||
)
|
||||
SliderSettingItem(
|
||||
title = stringResource(R.string.font_scale),
|
||||
description = stringResource(
|
||||
R.string.font_scale_summary,
|
||||
AppContextWrapper.getFontScale(context)
|
||||
),
|
||||
value = fontScaleValue.value,
|
||||
value = fontScaleValue.floatValue,
|
||||
defaultValue = 10f,
|
||||
valueRange = 8f..16f,
|
||||
steps = 7,
|
||||
@@ -478,7 +530,7 @@ fun ThemeConfigScreen(
|
||||
if (showRestartDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showRestartDialog = false },
|
||||
title = { Text(stringResource(R.string.restart_required_message)) },
|
||||
title = { AppText(stringResource(R.string.restart_required_message)) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
showRestartDialog = false
|
||||
@@ -486,7 +538,7 @@ fun ThemeConfigScreen(
|
||||
context.restart()
|
||||
}, 100)
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
@@ -494,7 +546,7 @@ fun ThemeConfigScreen(
|
||||
showRestartDialog = false
|
||||
context.toastOnUi(R.string.restart_later_message)
|
||||
}) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -502,57 +554,56 @@ fun ThemeConfigScreen(
|
||||
|
||||
manageKey?.let { isDark ->
|
||||
BackgroundImageManageSheet(
|
||||
show = true,
|
||||
isDarkTheme = isDark,
|
||||
onDismissRequest = { manageKey = null }
|
||||
)
|
||||
}
|
||||
|
||||
if (showColorPicker) {
|
||||
ColorPickerSheet(
|
||||
initialColor = primaryColorValue.value,
|
||||
onDismissRequest = { showColorPicker = false },
|
||||
onColorSelected = { color ->
|
||||
primaryColorValue.value = color
|
||||
ThemeConfig.cPrimary = color
|
||||
ThemeStore.editTheme(context)
|
||||
.primaryColor(color)
|
||||
.apply()
|
||||
DynamicColors.applyToActivitiesIfAvailable(
|
||||
context.applicationContext as android.app.Application,
|
||||
DynamicColorsOptions.Builder()
|
||||
.setContentBasedSource(context.primaryColor)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showLauncherIconPicker) {
|
||||
LauncherIconPickerSheet(
|
||||
selectedValue = ThemeConfig.launcherIcon,
|
||||
onDismissRequest = { showLauncherIconPicker = false },
|
||||
onValueChange = {
|
||||
ThemeConfig.launcherIcon = it
|
||||
LauncherIconHelp.changeIcon(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
ColorPickerSheet(
|
||||
show = showColorPicker,
|
||||
initialColor = primaryColorValue.value,
|
||||
onDismissRequest = { showColorPicker = false },
|
||||
onColorSelected = { color ->
|
||||
primaryColorValue.value = color
|
||||
ThemeConfig.cPrimary = color
|
||||
ThemeStore.editTheme(context)
|
||||
.primaryColor(color)
|
||||
.apply()
|
||||
DynamicColors.applyToActivitiesIfAvailable(
|
||||
context.applicationContext as android.app.Application,
|
||||
DynamicColorsOptions.Builder()
|
||||
.setContentBasedSource(context.primaryColor)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (showThemeListDialog) {
|
||||
ThemeListDialog(
|
||||
onDismissRequest = { showThemeListDialog = false }
|
||||
)
|
||||
}
|
||||
LauncherIconPickerSheet(
|
||||
show = showLauncherIconPicker,
|
||||
selectedValue = ThemeConfig.launcherIcon,
|
||||
onDismissRequest = { showLauncherIconPicker = false },
|
||||
onValueChange = {
|
||||
ThemeConfig.launcherIcon = it
|
||||
LauncherIconHelp.changeIcon(it)
|
||||
}
|
||||
)
|
||||
|
||||
ThemeListDialog(
|
||||
show = showThemeListDialog,
|
||||
onDismissRequest = { showThemeListDialog = false }
|
||||
)
|
||||
|
||||
saveThemeKey?.let { key ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { saveThemeKey = null },
|
||||
title = { Text(stringResource(R.string.theme_name)) },
|
||||
title = { AppText(stringResource(R.string.theme_name)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = themeName,
|
||||
onValueChange = { themeName = it },
|
||||
label = { Text("name") },
|
||||
label = { AppText("name") },
|
||||
singleLine = true
|
||||
)
|
||||
},
|
||||
@@ -566,12 +617,12 @@ fun ThemeConfigScreen(
|
||||
saveThemeKey = null
|
||||
}
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
AppText(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { saveThemeKey = null }) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -745,9 +796,9 @@ fun ThemeColorButton(
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,10 @@ import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.FileDownload
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -38,8 +36,10 @@ import androidx.core.graphics.toColorInt
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.config.OldThemeConfig
|
||||
import io.legado.app.lib.theme.primaryColor
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.GSON
|
||||
import io.legado.app.utils.getClipText
|
||||
import io.legado.app.utils.share
|
||||
@@ -48,6 +48,7 @@ import io.legado.app.utils.toastOnUi
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ThemeListDialog(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -55,7 +56,8 @@ fun ThemeListDialog(
|
||||
var deleteIndex by remember { mutableStateOf<Int?>(null) }
|
||||
val themeList = remember(listVersion) { OldThemeConfig.configList.toList() }
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Column(
|
||||
@@ -69,9 +71,9 @@ fun ThemeListDialog(
|
||||
.padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = stringResource(R.string.theme_list),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
IconButton(
|
||||
@@ -104,13 +106,11 @@ fun ThemeListDialog(
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
)
|
||||
containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -129,9 +129,9 @@ fun ThemeListDialog(
|
||||
)
|
||||
.padding(8.dp)
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = item.themeName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
style = LegadoTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
IconButton(
|
||||
@@ -161,8 +161,8 @@ fun ThemeListDialog(
|
||||
deleteIndex?.let { index ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { deleteIndex = null },
|
||||
title = { Text(stringResource(R.string.delete)) },
|
||||
text = { Text(stringResource(R.string.sure_del)) },
|
||||
title = { AppText(stringResource(R.string.delete)) },
|
||||
text = { AppText(stringResource(R.string.sure_del)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
@@ -171,12 +171,12 @@ fun ThemeListDialog(
|
||||
deleteIndex = null
|
||||
}
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
AppText(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { deleteIndex = null }) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -37,7 +36,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.DictRule
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -56,7 +56,8 @@ fun DictRuleEditSheet(
|
||||
var showRule by remember(rule) { mutableStateOf(rule?.showRule ?: "") }
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = true,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Box(
|
||||
@@ -69,7 +70,7 @@ fun DictRuleEditSheet(
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
CenterAlignedTopAppBar(
|
||||
title = { Text(stringResource(R.string.dict_rule)) },
|
||||
title = { AppText(stringResource(R.string.dict_rule)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
@@ -87,7 +88,7 @@ fun DictRuleEditSheet(
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.copy_rule)) },
|
||||
text = { AppText(stringResource(R.string.copy_rule)) },
|
||||
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
|
||||
onClick = {
|
||||
onCopy(
|
||||
@@ -102,7 +103,7 @@ fun DictRuleEditSheet(
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.paste_rule)) },
|
||||
text = { AppText(stringResource(R.string.paste_rule)) },
|
||||
leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -132,20 +133,20 @@ fun DictRuleEditSheet(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text(stringResource(R.string.name)) },
|
||||
label = { AppText(stringResource(R.string.name)) },
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = urlRule,
|
||||
onValueChange = { urlRule = it },
|
||||
label = { Text(stringResource(R.string.url_rule)) }
|
||||
label = { AppText(stringResource(R.string.url_rule)) }
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = showRule,
|
||||
onValueChange = { showRule = it },
|
||||
label = { Text(stringResource(R.string.show_rule)) },
|
||||
label = { AppText(stringResource(R.string.show_rule)) },
|
||||
minLines = 3
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -19,11 +18,9 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -51,7 +48,6 @@ import io.legado.app.ui.widget.components.DraggableSelectionHandler
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
|
||||
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
||||
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
@@ -59,6 +55,7 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.rules.RuleEditFields
|
||||
import io.legado.app.ui.widget.components.rules.RuleEditSheet
|
||||
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
|
||||
@@ -153,54 +150,49 @@ fun DictRuleScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showExportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
title = stringResource(R.string.export),
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportDoc.launch("exportDictRule.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showExportSheet,
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
title = stringResource(R.string.export),
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportDoc.launch("exportDictRule.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
|
||||
if (showImportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_dict_rule),
|
||||
onSelectSysFile = { types ->
|
||||
importDoc.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showUrlInput = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
}
|
||||
|
||||
(importState as? BaseImportUiState.Success<DictRule>)?.let { state ->
|
||||
BatchImportDialog(
|
||||
title = "导入词典规则",
|
||||
importState = state,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
itemContent = { rule, _ ->
|
||||
Column {
|
||||
Text(rule.name, style = MaterialTheme.typography.titleMedium)
|
||||
Text(rule.urlRule, style = MaterialTheme.typography.bodySmall, maxLines = 1)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showImportSheet,
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_dict_rule),
|
||||
onSelectSysFile = { types ->
|
||||
importDoc.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showUrlInput = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
|
||||
BatchImportDialog(
|
||||
title = "导入词典规则",
|
||||
importState = importState,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
itemTitle = { rule -> rule.name },
|
||||
itemSubtitle = { rule ->
|
||||
rule.urlRule.takeIf { it.isNotBlank() }
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(reorderableState.isAnyItemDragging) {
|
||||
if (!reorderableState.isAnyItemDragging) {
|
||||
@@ -211,16 +203,16 @@ fun DictRuleScreen(
|
||||
showDeleteRuleDialog?.let { rule ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteRuleDialog = null },
|
||||
title = { Text(stringResource(R.string.delete)) },
|
||||
text = { Text(stringResource(R.string.del_msg)) },
|
||||
title = { AppText(stringResource(R.string.delete)) },
|
||||
text = { AppText(stringResource(R.string.del_msg)) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.delete(rule); showDeleteRuleDialog = null
|
||||
}) { Text(stringResource(R.string.ok)) }
|
||||
}) { AppText(stringResource(R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteRuleDialog = null }) {
|
||||
Text(
|
||||
AppText(
|
||||
stringResource(R.string.cancel)
|
||||
)
|
||||
}
|
||||
@@ -228,47 +220,46 @@ fun DictRuleScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showEditSheet) {
|
||||
RuleEditSheet(
|
||||
rule = editingRule,
|
||||
title = stringResource(R.string.dict_rule),
|
||||
label1 = stringResource(R.string.url_rule),
|
||||
label2 = stringResource(R.string.show_rule),
|
||||
onDismissRequest = {
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onSave = { updatedRule ->
|
||||
if (editingRule == null) {
|
||||
viewModel.insert(updatedRule)
|
||||
} else {
|
||||
viewModel.update(updatedRule)
|
||||
}
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onCopy = { viewModel.copyRule(it) },
|
||||
onPaste = { viewModel.pasteRule() },
|
||||
toFields = { r ->
|
||||
RuleEditFields(
|
||||
name = r?.name ?: "",
|
||||
rule1 = r?.urlRule ?: "",
|
||||
rule2 = r?.showRule ?: ""
|
||||
)
|
||||
},
|
||||
fromFields = { fields, old ->
|
||||
old?.copy(
|
||||
name = fields.name,
|
||||
urlRule = fields.rule1,
|
||||
showRule = fields.rule2
|
||||
) ?: DictRule(
|
||||
name = fields.name,
|
||||
urlRule = fields.rule1,
|
||||
showRule = fields.rule2
|
||||
)
|
||||
RuleEditSheet(
|
||||
show = showEditSheet,
|
||||
rule = editingRule,
|
||||
title = stringResource(R.string.dict_rule),
|
||||
label1 = stringResource(R.string.url_rule),
|
||||
label2 = stringResource(R.string.show_rule),
|
||||
onDismissRequest = {
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onSave = { updatedRule ->
|
||||
if (editingRule == null) {
|
||||
viewModel.insert(updatedRule)
|
||||
} else {
|
||||
viewModel.update(updatedRule)
|
||||
}
|
||||
)
|
||||
}
|
||||
showEditSheet = false
|
||||
editingRule = null
|
||||
},
|
||||
onCopy = { viewModel.copyRule(it) },
|
||||
onPaste = { viewModel.pasteRule() },
|
||||
toFields = { r ->
|
||||
RuleEditFields(
|
||||
name = r?.name ?: "",
|
||||
rule1 = r?.urlRule ?: "",
|
||||
rule2 = r?.showRule ?: ""
|
||||
)
|
||||
},
|
||||
fromFields = { fields, old ->
|
||||
old?.copy(
|
||||
name = fields.name,
|
||||
urlRule = fields.rule1,
|
||||
showRule = fields.rule2
|
||||
) ?: DictRule(
|
||||
name = fields.name,
|
||||
urlRule = fields.rule1,
|
||||
showRule = fields.rule2
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
RuleListScaffold(
|
||||
title = "字典规则",
|
||||
@@ -310,7 +301,7 @@ fun DictRuleScreen(
|
||||
snackbarHostState = snackbarHostState,
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_str)) },
|
||||
text = stringResource(R.string.import_str),
|
||||
onClick = { showImportSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
|
||||
)
|
||||
|
||||
@@ -181,7 +181,7 @@ open class MainActivity : BaseComposeActivity() {
|
||||
(slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(
|
||||
durationMillis = 520,
|
||||
durationMillis = 480,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffset = { fullWidth -> fullWidth }
|
||||
@@ -193,7 +193,7 @@ open class MainActivity : BaseComposeActivity() {
|
||||
)) togetherWith (slideOutOfContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(
|
||||
durationMillis = 520,
|
||||
durationMillis = 480,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
targetOffset = { fullWidth -> fullWidth / 4 }
|
||||
@@ -208,7 +208,7 @@ open class MainActivity : BaseComposeActivity() {
|
||||
(slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(
|
||||
durationMillis = 520,
|
||||
durationMillis = 480,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffset = { fullWidth -> -fullWidth / 4 }
|
||||
@@ -220,7 +220,7 @@ open class MainActivity : BaseComposeActivity() {
|
||||
)) togetherWith (scaleOut(
|
||||
targetScale = 0.8f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 520,
|
||||
durationMillis = 480,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
) + fadeOut(
|
||||
|
||||
@@ -12,42 +12,57 @@ import androidx.compose.material.icons.outlined.Person
|
||||
import androidx.compose.material.icons.outlined.RssFeed
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import io.legado.app.R
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.extended.Album
|
||||
import top.yukonga.miuix.kmp.icon.extended.ContactsBook
|
||||
import top.yukonga.miuix.kmp.icon.extended.Favorites
|
||||
import top.yukonga.miuix.kmp.icon.extended.Settings
|
||||
|
||||
sealed class MainDestination(
|
||||
val route: String,
|
||||
@StringRes val labelId: Int,
|
||||
val icon: ImageVector,
|
||||
val selectedIcon: ImageVector
|
||||
val m3Icon: ImageVector,
|
||||
val m3SelectedIcon: ImageVector,
|
||||
val miuixIcon: ImageVector,
|
||||
val miuixSelectedIcon: ImageVector
|
||||
) {
|
||||
object Bookshelf : MainDestination(
|
||||
"bookshelf",
|
||||
R.string.bookshelf,
|
||||
Icons.AutoMirrored.Outlined.LibraryBooks,
|
||||
Icons.AutoMirrored.Filled.LibraryBooks
|
||||
route = "bookshelf",
|
||||
labelId = R.string.bookshelf,
|
||||
m3Icon = Icons.AutoMirrored.Outlined.LibraryBooks,
|
||||
m3SelectedIcon = Icons.AutoMirrored.Filled.LibraryBooks,
|
||||
miuixIcon = MiuixIcons.Regular.ContactsBook, // 替换为实际的 MIUIX 线框图标
|
||||
miuixSelectedIcon = MiuixIcons.Heavy.ContactsBook // 替换为实际的 MIUIX 填充图标
|
||||
)
|
||||
|
||||
object Explore : MainDestination(
|
||||
"explore",
|
||||
R.string.discovery,
|
||||
Icons.Outlined.Explore,
|
||||
Icons.Default.Explore
|
||||
route = "explore",
|
||||
labelId = R.string.discovery,
|
||||
m3Icon = Icons.Outlined.Explore,
|
||||
m3SelectedIcon = Icons.Default.Explore,
|
||||
miuixIcon = MiuixIcons.Regular.Album,
|
||||
miuixSelectedIcon = MiuixIcons.Heavy.Album
|
||||
)
|
||||
|
||||
object Rss : MainDestination(
|
||||
"rss",
|
||||
R.string.rss,
|
||||
Icons.Outlined.RssFeed,
|
||||
Icons.Default.RssFeed
|
||||
route = "rss",
|
||||
labelId = R.string.rss,
|
||||
m3Icon = Icons.Outlined.RssFeed,
|
||||
m3SelectedIcon = Icons.Default.RssFeed,
|
||||
miuixIcon = MiuixIcons.Regular.Favorites,
|
||||
miuixSelectedIcon = MiuixIcons.Heavy.Favorites
|
||||
)
|
||||
|
||||
object My : MainDestination(
|
||||
"my",
|
||||
R.string.my,
|
||||
Icons.Outlined.Person,
|
||||
Icons.Default.Person
|
||||
route = "my",
|
||||
labelId = R.string.my,
|
||||
m3Icon = Icons.Outlined.Person,
|
||||
m3SelectedIcon = Icons.Default.Person,
|
||||
miuixIcon = MiuixIcons.Regular.Settings,
|
||||
miuixSelectedIcon = MiuixIcons.Heavy.Settings
|
||||
)
|
||||
|
||||
companion object {
|
||||
val mainDestinations = listOf(Bookshelf, Explore, Rss, My)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,12 @@ import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.BottomAppBarDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.WideNavigationRail
|
||||
import androidx.compose.material3.WideNavigationRailItem
|
||||
@@ -56,17 +52,19 @@ import io.legado.app.R
|
||||
import io.legado.app.ui.book.info.BookInfoActivity
|
||||
import io.legado.app.ui.book.search.SearchActivity
|
||||
import io.legado.app.ui.config.mainConfig.MainConfig
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.main.bookshelf.BookshelfScreen
|
||||
import io.legado.app.ui.main.bookshelf.BookshelfViewModel
|
||||
import io.legado.app.ui.main.explore.ExploreScreen
|
||||
import io.legado.app.ui.main.my.MyScreen
|
||||
import io.legado.app.ui.main.rss.RssScreen
|
||||
import io.legado.app.ui.theme.regularHazeEffect
|
||||
import io.legado.app.ui.widget.components.AppNavigationBar
|
||||
import io.legado.app.ui.widget.components.AppNavigationBarItem
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassDefaults
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.startActivity
|
||||
import io.legado.app.utils.startActivityForBook
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -151,7 +149,7 @@ fun MainScreen(
|
||||
onClick = { context.startActivity<SearchActivity>() },
|
||||
expanded = expanded,
|
||||
icon = { Icon(Icons.Default.Search, contentDescription = null) },
|
||||
text = { Text(stringResource(R.string.search)) }
|
||||
text = { AppText(stringResource(R.string.search)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -198,7 +196,7 @@ fun MainScreen(
|
||||
) { dismiss ->
|
||||
bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(group.groupName) },
|
||||
text = group.groupName,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
if (pagerState.currentPage != index) {
|
||||
@@ -224,7 +222,7 @@ fun MainScreen(
|
||||
}
|
||||
},
|
||||
label = if (labelVisibilityMode != "unlabeled") {
|
||||
{ Text(stringResource(destination.labelId)) }
|
||||
{ AppText(stringResource(destination.labelId)) }
|
||||
} else null
|
||||
)
|
||||
}
|
||||
@@ -237,103 +235,38 @@ fun MainScreen(
|
||||
if (!useRail && MainConfig.showBottomView) {
|
||||
val labelVisibilityMode = MainConfig.labelVisibilityMode
|
||||
val isUnlabeled = labelVisibilityMode == "unlabeled"
|
||||
NavigationBar(
|
||||
|
||||
AppNavigationBar(
|
||||
modifier = Modifier
|
||||
.regularHazeEffect(state = hazeState)
|
||||
.height(if (isUnlabeled) 64.dp else 80.dp),
|
||||
containerColor = GlassDefaults.glassColor(
|
||||
noBlurColor = BottomAppBarDefaults.containerColor,
|
||||
blurAlpha = GlassDefaults.DefaultBlurAlpha
|
||||
).let { baseColor ->
|
||||
val opacity = (ThemeConfig.bottomBarOpacity.coerceIn(0, 100)) / 100f
|
||||
baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
|
||||
}
|
||||
.height(if (isUnlabeled) 64.dp else 80.dp)
|
||||
) {
|
||||
val alwaysShowLabel = when (labelVisibilityMode) {
|
||||
"labeled" -> true
|
||||
"selected" -> false
|
||||
"unlabeled" -> false
|
||||
else -> false
|
||||
}
|
||||
destinations.forEachIndexed { index, destination ->
|
||||
val selected = pagerState.targetPage == index
|
||||
var showGroupMenu by remember { mutableStateOf(false) }
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
NavigationBarItem(
|
||||
AppNavigationBarItem(
|
||||
selected = selected,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(index)
|
||||
}
|
||||
coroutineScope.launch { pagerState.animateScrollToPage(index) }
|
||||
},
|
||||
icon = {
|
||||
Box {
|
||||
NavigationIcon(
|
||||
destination = destination,
|
||||
selected = selected,
|
||||
upBooksCount = uiState.upBooksCount,
|
||||
modifier = if (destination == MainDestination.Bookshelf) {
|
||||
Modifier.combinedClickable(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(index)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
haptic.performHapticFeedback(
|
||||
HapticFeedbackType.LongPress
|
||||
)
|
||||
showGroupMenu = true
|
||||
}
|
||||
)
|
||||
} else Modifier
|
||||
)
|
||||
|
||||
if (destination == MainDestination.Bookshelf && showGroupMenu) {
|
||||
RoundDropdownMenu(
|
||||
expanded = showGroupMenu,
|
||||
onDismissRequest = { showGroupMenu = false }
|
||||
) { dismiss ->
|
||||
bookshelfUiState.groups.forEachIndexed { groupIndex, group ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(group.groupName) },
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
if (pagerState.currentPage != index) {
|
||||
pagerState.scrollToPage(index)
|
||||
}
|
||||
bookshelfViewModel.changeGroup(
|
||||
group.groupId
|
||||
)
|
||||
dismiss()
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
if (bookshelfUiState.selectedGroupIndex == groupIndex) {
|
||||
Icon(
|
||||
Icons.Default.Check,
|
||||
null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
labelString = stringResource(destination.labelId),
|
||||
miuixIcon = if (selected) destination.miuixSelectedIcon else destination.miuixIcon,
|
||||
m3Icon = {
|
||||
NavigationIcon(
|
||||
destination = destination,
|
||||
selected = selected,
|
||||
upBooksCount = uiState.upBooksCount
|
||||
)
|
||||
},
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
indicatorColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
blurAlpha = GlassDefaults.ThickBlurAlpha
|
||||
),
|
||||
m3IndicatorColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
blurAlpha = GlassDefaults.ThickBlurAlpha
|
||||
),
|
||||
label = if (labelVisibilityMode != "unlabeled") {
|
||||
{ Text(stringResource(destination.labelId)) }
|
||||
} else null,
|
||||
alwaysShowLabel = alwaysShowLabel
|
||||
m3ShowLabel = !isUnlabeled,
|
||||
m3AlwaysShowLabel = alwaysShowLabel
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -386,7 +319,7 @@ private fun NavigationIcon(
|
||||
upBooksCount: Int,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val icon = if (selected) destination.selectedIcon else destination.icon
|
||||
val icon = if (selected) destination.m3SelectedIcon else destination.m3Icon
|
||||
Box(modifier = modifier) {
|
||||
if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
|
||||
BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -34,9 +33,11 @@ import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.cover.BookCover
|
||||
import io.legado.app.ui.widget.components.cover.BookshelfCover
|
||||
import io.legado.app.ui.widget.components.cover.Cover
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.toTimeAgo
|
||||
|
||||
/**
|
||||
@@ -88,9 +89,9 @@ fun BookshelfItem(
|
||||
) {
|
||||
cover(Modifier.fillMaxSize())
|
||||
if (gridStyle == 1) {
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = (if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium).copy(
|
||||
style = (if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium).copy(
|
||||
color = Color.White,
|
||||
shadow = Shadow(
|
||||
color = Color.Black.copy(alpha = 0.5f),
|
||||
@@ -117,9 +118,9 @@ fun BookshelfItem(
|
||||
}
|
||||
|
||||
if (gridStyle == 0) {
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = if (titleSmallFont) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium,
|
||||
style = if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium,
|
||||
maxLines = titleMaxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
|
||||
@@ -152,16 +153,16 @@ fun BookshelfItem(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
|
||||
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
|
||||
maxLines = if (!isCompact) 2 else 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
subTitle?.let {
|
||||
Text(
|
||||
AppText(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
@@ -169,9 +170,9 @@ fun BookshelfItem(
|
||||
}
|
||||
if (!isCompact) {
|
||||
desc?.let {
|
||||
Text(
|
||||
AppText(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
@@ -372,7 +373,7 @@ fun BookItem(
|
||||
isUpdating = isUpdating,
|
||||
modifier = modifier,
|
||||
badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
|
||||
showBadgeDot = !BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && unreadCount > 0
|
||||
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew
|
||||
)
|
||||
},
|
||||
title = book.name,
|
||||
@@ -384,16 +385,16 @@ fun BookItem(
|
||||
desc = stringResource(R.string.read_dur_progress, book.durChapterTitle ?: ""),
|
||||
extra = {
|
||||
if (BookshelfConfig.showLastUpdateTime && !book.isLocal) {
|
||||
Text(
|
||||
AppText(
|
||||
text = book.latestChapterTime.toTimeAgo(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier.padding(end = 4.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
AppText(
|
||||
text = book.latestChapterTitle ?: "",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
|
||||
@@ -16,6 +16,7 @@ data class BookShelfItem(
|
||||
val durChapterPos: Int,
|
||||
val latestChapterTitle: String?,
|
||||
val latestChapterTime: Long,
|
||||
val lastCheckCount: Int,
|
||||
val totalChapterNum: Int,
|
||||
val durChapterIndex: Int,
|
||||
val type: Int,
|
||||
@@ -33,5 +34,8 @@ data class BookShelfItem(
|
||||
|
||||
val isNotShelf: Boolean get() = (type and BookType.notShelf) > 0
|
||||
|
||||
val isNew: Boolean get() = lastCheckCount > 0
|
||||
|
||||
|
||||
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.material.icons.filled.TextFormat
|
||||
import androidx.compose.material.icons.filled.ViewCompact
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
@@ -32,7 +31,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.CompactSliderSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
|
||||
@@ -40,28 +40,28 @@ import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BookshelfConfigSheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
|
||||
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
|
||||
AppModalBottomSheet(
|
||||
title = stringResource(R.string.bookshelf_layout),
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
GlassCard() {
|
||||
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.animateContentSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 32.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.bookshelf_layout),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
|
||||
// Group Style
|
||||
CompactDropdownSettingItem(
|
||||
title = stringResource(R.string.group_style),
|
||||
selectedValue = BookshelfConfig.bookGroupStyle.toString(),
|
||||
|
||||
@@ -40,12 +40,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||
import androidx.compose.material3.pulltorefresh.pullToRefresh
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
@@ -60,13 +57,11 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import io.legado.app.R
|
||||
@@ -78,6 +73,7 @@ import io.legado.app.ui.book.import.remote.RemoteBookActivity
|
||||
import io.legado.app.ui.book.manage.BookshelfManageActivity
|
||||
import io.legado.app.ui.book.search.SearchActivity
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
||||
@@ -85,6 +81,8 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
|
||||
import io.legado.app.ui.widget.components.list.ListScaffold
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.readText
|
||||
import io.legado.app.utils.startActivity
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@@ -237,37 +235,37 @@ fun BookshelfScreen(
|
||||
topBarActions = { },
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.add_remote_book)) },
|
||||
text = stringResource(R.string.add_remote_book),
|
||||
onClick = { context.startActivity<RemoteBookActivity>(); dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.Wifi, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.book_local)) },
|
||||
text = stringResource(R.string.book_local),
|
||||
onClick = { context.startActivity<ImportBookActivity>(); dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.Save, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.update_toc)) },
|
||||
text = stringResource(R.string.update_toc),
|
||||
onClick = { viewModel.upToc(uiState.items); dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.Refresh, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("布局设置") },
|
||||
text = "布局设置",
|
||||
onClick = { showConfigSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.GridView, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.group_manage)) },
|
||||
text = stringResource(R.string.group_manage),
|
||||
onClick = { showGroupManageSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.Edit, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.add_url)) },
|
||||
text = stringResource(R.string.add_url),
|
||||
onClick = { showAddUrlDialog = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.Link, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.bookshelf_management)) },
|
||||
text = stringResource(R.string.bookshelf_management),
|
||||
onClick = {
|
||||
val groupId =
|
||||
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
|
||||
@@ -279,7 +277,7 @@ fun BookshelfScreen(
|
||||
leadingIcon = { Icon(Icons.Default.Settings, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.cache_export)) },
|
||||
text = stringResource(R.string.cache_export),
|
||||
onClick = {
|
||||
val groupId =
|
||||
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
|
||||
@@ -291,7 +289,7 @@ fun BookshelfScreen(
|
||||
leadingIcon = { Icon(Icons.Default.Download, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.export_bookshelf)) },
|
||||
text = stringResource(R.string.export_bookshelf),
|
||||
onClick = {
|
||||
showExportSheet = true
|
||||
dismiss()
|
||||
@@ -299,12 +297,12 @@ fun BookshelfScreen(
|
||||
leadingIcon = { Icon(Icons.Default.ImportExport, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_bookshelf)) },
|
||||
text = stringResource(R.string.import_bookshelf),
|
||||
onClick = { showImportSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.log)) },
|
||||
text = stringResource(R.string.log),
|
||||
onClick = {
|
||||
showLogSheet = true
|
||||
dismiss()
|
||||
@@ -322,33 +320,19 @@ fun BookshelfScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
edgePadding = 0.dp,
|
||||
divider = { },
|
||||
containerColor = Color.Transparent,
|
||||
minTabWidth = 0.dp,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
uiState.groups.forEachIndexed { index, group ->
|
||||
Tab(
|
||||
selected = selectedTabIndex == index,
|
||||
onClick = {
|
||||
scope.launch { pagerState.animateScrollToPage(index) }
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = group.groupName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
val tabTitles = remember(uiState.groups) {
|
||||
uiState.groups.map { it.groupName }
|
||||
}
|
||||
|
||||
AppTabRow(
|
||||
tabTitles = tabTitles,
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
onTabSelected = { index ->
|
||||
scope.launch { pagerState.animateScrollToPage(index) }
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (BookshelfConfig.shouldShowExpandButton) {
|
||||
Box(modifier = Modifier.padding(end = 16.dp)) {
|
||||
SmallOutlinedIconToggleButton(
|
||||
@@ -363,7 +347,7 @@ fun BookshelfScreen(
|
||||
) { dismiss ->
|
||||
uiState.groups.forEachIndexed { index, group ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(group.groupName) },
|
||||
text = group.groupName,
|
||||
onClick = {
|
||||
scope.launch { pagerState.animateScrollToPage(index) }
|
||||
dismiss()
|
||||
@@ -502,13 +486,15 @@ fun BookshelfScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showConfigSheet) {
|
||||
BookshelfConfigSheet(onDismissRequest = { showConfigSheet = false })
|
||||
}
|
||||
BookshelfConfigSheet(
|
||||
show = showConfigSheet,
|
||||
onDismissRequest = { showConfigSheet = false }
|
||||
)
|
||||
|
||||
if (showGroupManageSheet) {
|
||||
GroupManageSheet(onDismissRequest = { showGroupManageSheet = false })
|
||||
}
|
||||
GroupManageSheet(
|
||||
show = showGroupManageSheet,
|
||||
onDismissRequest = { showGroupManageSheet = false }
|
||||
)
|
||||
|
||||
if (showAddUrlDialog) {
|
||||
SourceInputDialog(
|
||||
@@ -521,40 +507,39 @@ fun BookshelfScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showImportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_bookshelf),
|
||||
onSelectSysFile = { types ->
|
||||
importLauncher.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showAddUrlDialog = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showImportSheet,
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_bookshelf),
|
||||
onSelectSysFile = { types ->
|
||||
importLauncher.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showAddUrlDialog = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
|
||||
if (showExportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
title = stringResource(R.string.export_bookshelf),
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportLauncher.launch("bookshelf.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadBookshelf(uiState.items)
|
||||
}
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showExportSheet,
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
title = stringResource(R.string.export_bookshelf),
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportLauncher.launch("bookshelf.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadBookshelf(uiState.items)
|
||||
}
|
||||
)
|
||||
|
||||
if (showLogSheet) {
|
||||
AppLogSheet(onDismissRequest = { showLogSheet = false })
|
||||
}
|
||||
AppLogSheet(
|
||||
show = showLogSheet,
|
||||
onDismissRequest = { showLogSheet = false }
|
||||
)
|
||||
|
||||
if (uiState.isLoading) {
|
||||
Dialog(onDismissRequest = {}) {
|
||||
@@ -568,10 +553,10 @@ fun BookshelfScreen(
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
uiState.loadingText?.let {
|
||||
Text(
|
||||
AppText(
|
||||
text = it,
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = LegadoTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,14 @@ import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -27,7 +21,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -36,8 +29,9 @@ import io.legado.app.R
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.ui.book.group.GroupEditContent
|
||||
import io.legado.app.ui.book.group.GroupViewModel
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.utils.move
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
@@ -45,6 +39,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GroupManageSheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: GroupViewModel = koinViewModel(),
|
||||
bookshelfViewModel: BookshelfViewModel = koinViewModel()
|
||||
@@ -77,7 +72,22 @@ fun GroupManageSheet(
|
||||
}
|
||||
}
|
||||
|
||||
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = if (!isEditing) stringResource(R.string.group_manage) else stringResource(R.string.group_edit),
|
||||
endAction = {
|
||||
if (!isEditing) {
|
||||
SmallIconButton(
|
||||
onClick = {
|
||||
editingGroup = null
|
||||
isEditing = true
|
||||
},
|
||||
icon = Icons.Default.Add
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = isEditing,
|
||||
transitionSpec = {
|
||||
@@ -92,56 +102,28 @@ fun GroupManageSheet(
|
||||
viewModel = viewModel
|
||||
)
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.group_manage),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
items(listData, key = { it.groupId }) { group ->
|
||||
val manageNameInfo = remember(group) { group.getManageName(context) }
|
||||
ReorderableSelectionItem(
|
||||
state = reorderableState,
|
||||
key = group.groupId,
|
||||
title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
|
||||
subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
|
||||
isEnabled = group.show,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
onEnabledChange = { isChecked ->
|
||||
viewModel.upGroup(group.copy(show = isChecked))
|
||||
},
|
||||
onClickEdit = {
|
||||
editingGroup = group
|
||||
isEditing = true
|
||||
}
|
||||
)
|
||||
IconButton(onClick = {
|
||||
editingGroup = null
|
||||
isEditing = true
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
contentDescription = stringResource(R.string.add)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
items(listData, key = { it.groupId }) { group ->
|
||||
val manageNameInfo = remember(group) { group.getManageName(context) }
|
||||
ReorderableSelectionItem(
|
||||
state = reorderableState,
|
||||
key = group.groupId,
|
||||
title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
|
||||
subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
|
||||
isEnabled = group.show,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
onEnabledChange = { isChecked ->
|
||||
viewModel.upGroup(group.copy(show = isChecked))
|
||||
},
|
||||
onClickEdit = {
|
||||
editingGroup = group
|
||||
isEditing = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@ import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@@ -73,6 +71,9 @@ import io.legado.app.ui.book.search.SearchActivity
|
||||
import io.legado.app.ui.book.search.SearchScope
|
||||
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
||||
import io.legado.app.ui.login.SourceLoginActivity
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
|
||||
@@ -81,9 +82,11 @@ import io.legado.app.ui.widget.components.list.ListScaffold
|
||||
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.startActivity
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@@ -138,6 +141,8 @@ fun ExploreScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
|
||||
|
||||
ListScaffold(
|
||||
title = stringResource(R.string.discovery),
|
||||
state = uiState,
|
||||
@@ -148,13 +153,13 @@ fun ExploreScreen(
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Group) },
|
||||
text = { Text(stringResource(R.string.all)) },
|
||||
text = stringResource(R.string.all),
|
||||
onClick = { viewModel.setGroup(""); dismiss() }
|
||||
)
|
||||
uiState.groups.forEach { group ->
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
|
||||
text = { Text(group) },
|
||||
text = group,
|
||||
onClick = { viewModel.setGroup(group); dismiss() }
|
||||
)
|
||||
}
|
||||
@@ -197,7 +202,8 @@ fun ExploreScreen(
|
||||
}
|
||||
},
|
||||
onRefresh = { viewModel.refreshExploreKinds(item) },
|
||||
onDelete = { sourceToDelete = item }
|
||||
onDelete = { sourceToDelete = item },
|
||||
isMiuix = composeEngine
|
||||
)
|
||||
}
|
||||
|
||||
@@ -228,7 +234,8 @@ fun ExploreScreen(
|
||||
putExtra("exploreUrl", kind.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isMiuix = composeEngine
|
||||
)
|
||||
}
|
||||
|
||||
@@ -264,7 +271,8 @@ fun ExploreScreen(
|
||||
uiState.items.indexOfFirst { it.bookSourceUrl == item.bookSourceUrl }
|
||||
if (index >= 0) listState.animateScrollToItem(index)
|
||||
}
|
||||
}
|
||||
},
|
||||
isMiuix = composeEngine
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -274,19 +282,19 @@ fun ExploreScreen(
|
||||
sourceToDelete?.let { source ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { sourceToDelete = null },
|
||||
title = { Text(stringResource(R.string.draw)) },
|
||||
text = { Text(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
|
||||
title = { AppText(stringResource(R.string.draw)) },
|
||||
text = { AppText(stringResource(R.string.sure_del) + "\n" + source.bookSourceName) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
viewModel.deleteSource(source)
|
||||
sourceToDelete = null
|
||||
}) {
|
||||
Text(stringResource(R.string.yes))
|
||||
AppText(stringResource(R.string.yes))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { sourceToDelete = null }) {
|
||||
Text(stringResource(R.string.no))
|
||||
AppText(stringResource(R.string.no))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -341,14 +349,25 @@ fun ExploreSourceHeader(
|
||||
onLogin: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
isMiuix: Boolean,
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
val rotation by animateFloatAsState(if (isExpanded) 90f else 0f, label = "rotation")
|
||||
|
||||
val containerColor by animateColorAsState(
|
||||
targetValue = if (isExpanded)
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
if (isMiuix) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
|
||||
else
|
||||
MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
if (isMiuix) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
|
||||
label = "CardColor"
|
||||
)
|
||||
|
||||
val contentColor by animateColorAsState(
|
||||
targetValue = if (isExpanded)
|
||||
if (isMiuix) MiuixTheme.colorScheme.primary else MaterialTheme.colorScheme.primary
|
||||
else
|
||||
if (isMiuix) MiuixTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurface,
|
||||
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
|
||||
label = "CardColor"
|
||||
)
|
||||
@@ -358,7 +377,7 @@ fun ExploreSourceHeader(
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = CardDefaults.cardColors(containerColor = containerColor),
|
||||
containerColor = containerColor
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
@@ -371,14 +390,10 @@ fun ExploreSourceHeader(
|
||||
containerColor = Color.Transparent
|
||||
),
|
||||
headlineContent = {
|
||||
Text(
|
||||
AppText(
|
||||
text = item.bookSourceName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = if (isExpanded) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
style = LegadoTheme.typography.titleMedium,
|
||||
color = contentColor
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
@@ -401,29 +416,29 @@ fun ExploreSourceHeader(
|
||||
PillHeaderDivider(title = item.bookSourceName)
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
|
||||
text = { Text(stringResource(R.string.to_top)) },
|
||||
text = stringResource(R.string.to_top),
|
||||
onClick = { onTop(); showMenu = false }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
text = stringResource(R.string.edit),
|
||||
onClick = { onEdit(); showMenu = false }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Search) },
|
||||
text = { Text(stringResource(R.string.search)) },
|
||||
text = stringResource(R.string.search),
|
||||
onClick = { onSearch(); showMenu = false }
|
||||
)
|
||||
if (item.hasLoginUrl) {
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
|
||||
text = { Text(stringResource(R.string.login)) },
|
||||
text = stringResource(R.string.login),
|
||||
onClick = { onLogin(); showMenu = false }
|
||||
)
|
||||
}
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Refresh) },
|
||||
text = { Text(stringResource(R.string.refresh)) },
|
||||
text = stringResource(R.string.refresh),
|
||||
onClick = { onRefresh(); showMenu = false }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
@@ -433,12 +448,8 @@ fun ExploreSourceHeader(
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
stringResource(R.string.delete),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
},
|
||||
text = stringResource(R.string.delete),
|
||||
color = LegadoTheme.colorScheme.error,
|
||||
onClick = { onDelete(); showMenu = false }
|
||||
)
|
||||
}
|
||||
@@ -450,12 +461,12 @@ fun ExploreSourceHeader(
|
||||
@Composable
|
||||
fun ExploreStickyCard(
|
||||
item: BookSourcePart,
|
||||
onClick: () -> Unit
|
||||
onClick: () -> Unit,
|
||||
isMiuix: Boolean,
|
||||
) {
|
||||
TextCard(
|
||||
text = item.bookSourceName,
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
textStyle = MaterialTheme.typography.labelLarge,
|
||||
textStyle = LegadoTheme.typography.labelLarge,
|
||||
horizontalPadding = 8.dp,
|
||||
verticalPadding = 6.dp,
|
||||
onClick = onClick
|
||||
@@ -467,8 +478,29 @@ fun ExploreKindItem(
|
||||
kind: ExploreKind,
|
||||
isClickable: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
isMiuix: Boolean,
|
||||
) {
|
||||
val color = if (isMiuix)
|
||||
MiuixTheme.colorScheme.surfaceContainer
|
||||
else
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
|
||||
val contentColor = if (isMiuix)
|
||||
MiuixTheme.colorScheme.onSurface
|
||||
else
|
||||
MaterialTheme.colorScheme.secondary
|
||||
|
||||
val unClickBackColor = if (isMiuix)
|
||||
MiuixTheme.colorScheme.surfaceContainer
|
||||
else
|
||||
MaterialTheme.colorScheme.surface
|
||||
|
||||
val unClickColor = if (isMiuix)
|
||||
MiuixTheme.colorScheme.disabledOnSurface
|
||||
else
|
||||
MaterialTheme.colorScheme.primary
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMinimumInteractiveComponentSize provides Dp.Unspecified
|
||||
) {
|
||||
@@ -479,21 +511,19 @@ fun ExploreKindItem(
|
||||
GlassCard(
|
||||
onClick = onClick,
|
||||
shape = shape,
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.6f),
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
),
|
||||
containerColor = color.copy(alpha = 0.6f),
|
||||
contentColor = contentColor,
|
||||
modifier = modifier
|
||||
) {
|
||||
KindText(kind)
|
||||
}
|
||||
} else {
|
||||
OutlinedCard(
|
||||
GlassCard(
|
||||
shape = shape,
|
||||
colors = CardDefaults.outlinedCardColors(
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
modifier = modifier
|
||||
containerColor = unClickBackColor.copy(alpha = 0.6f),
|
||||
contentColor = unClickColor,
|
||||
modifier = modifier,
|
||||
border = CardDefaults.outlinedCardBorder()
|
||||
) {
|
||||
KindText(kind)
|
||||
}
|
||||
@@ -506,12 +536,12 @@ fun ExploreKindItem(
|
||||
private fun KindText(
|
||||
kind: ExploreKind
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = kind.title,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
style = MaterialTheme.typography.labelMediumEmphasized,
|
||||
style = LegadoTheme.typography.labelMediumEmphasized,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package io.legado.app.ui.main.my
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -16,20 +23,19 @@ import androidx.compose.material.icons.automirrored.filled.HelpOutline
|
||||
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
||||
import androidx.compose.material.icons.automirrored.filled.Rule
|
||||
import androidx.compose.material.icons.filled.Bookmark
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.FindReplace
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.OpenInBrowser
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Source
|
||||
import androidx.compose.material.icons.filled.Web
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -46,11 +52,14 @@ import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
||||
import io.legado.app.ui.dict.rule.DictRuleActivity
|
||||
import io.legado.app.ui.file.FileManageActivity
|
||||
import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.settingItem.SettingItem
|
||||
import io.legado.app.ui.widget.components.button.SmallTextButton
|
||||
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -70,11 +79,7 @@ fun MyScreen(
|
||||
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.my)
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.my),
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { onNavigate(PrefClickEvent.ShowMd("appHelp", "xxx")) }
|
||||
@@ -99,52 +104,21 @@ fun MyScreen(
|
||||
)
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.adaptiveHorizontalPadding(),
|
||||
title = ""
|
||||
) {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.web_service),
|
||||
description = if (uiState.isWebServiceRun)
|
||||
uiState.webServiceAddress
|
||||
else
|
||||
stringResource(R.string.web_service_desc),
|
||||
imageVector = Icons.Default.Web,
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = uiState.isWebServiceRun,
|
||||
onCheckedChange = {
|
||||
viewModel.onEvent(PrefClickEvent.ToggleWebService)
|
||||
}
|
||||
)
|
||||
},
|
||||
dropdownMenu = if (uiState.isWebServiceRun) {
|
||||
{ onDismiss ->
|
||||
DropdownMenuItem(
|
||||
text = { Text("复制地址") },
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("浏览器打开") },
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
onClick = { }
|
||||
WebServiceSettingBlock(
|
||||
uiState = uiState,
|
||||
viewModel = viewModel,
|
||||
onNavigate = onNavigate
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.adaptiveHorizontalPadding(),
|
||||
title = "规则"
|
||||
) {
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.book_source_manage),
|
||||
description = stringResource(R.string.book_source_manage_desc),
|
||||
imageVector = Icons.Default.Source,
|
||||
@@ -154,7 +128,7 @@ fun MyScreen(
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.replace_purify),
|
||||
imageVector = Icons.Default.FindReplace,
|
||||
onClick = {
|
||||
@@ -163,7 +137,7 @@ fun MyScreen(
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.txt_toc_rule),
|
||||
imageVector = Icons.AutoMirrored.Filled.Rule,
|
||||
onClick = {
|
||||
@@ -172,7 +146,7 @@ fun MyScreen(
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.dict_rule),
|
||||
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
|
||||
onClick = {
|
||||
@@ -184,44 +158,44 @@ fun MyScreen(
|
||||
}
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.adaptiveHorizontalPadding(),
|
||||
title = stringResource(R.string.other)) {
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.setting),
|
||||
imageVector = Icons.Default.Settings,
|
||||
onClick = {
|
||||
onOpenSettings()
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.bookmark),
|
||||
imageVector = Icons.Default.Bookmark,
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.StartActivity(AllBookmarkActivity::class.java))
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.read_record),
|
||||
imageVector = Icons.Default.History,
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.StartActivity(ReadRecordActivity::class.java))
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.file_manage),
|
||||
imageVector = Icons.Default.Folder,
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.StartActivity(FileManageActivity::class.java))
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.about),
|
||||
imageVector = Icons.Default.Info,
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.StartActivity(AboutActivity::class.java))
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.exit),
|
||||
imageVector = Icons.AutoMirrored.Filled.ExitToApp,
|
||||
onClick = {
|
||||
@@ -234,3 +208,58 @@ fun MyScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun WebServiceSettingBlock(
|
||||
uiState: MyUiState,
|
||||
viewModel: MyViewModel,
|
||||
onNavigate: (PrefClickEvent) -> Unit
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SwitchSettingItem(
|
||||
title = stringResource(R.string.web_service),
|
||||
description = if (uiState.isWebServiceRun) {
|
||||
uiState.webServiceAddress
|
||||
} else {
|
||||
stringResource(R.string.web_service_desc)
|
||||
},
|
||||
imageVector = Icons.Default.Web,
|
||||
checked = uiState.isWebServiceRun,
|
||||
onCheckedChange = {
|
||||
viewModel.onEvent(PrefClickEvent.ToggleWebService)
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = uiState.isWebServiceRun,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
SmallTextButton(
|
||||
text = "复制地址",
|
||||
icon = Icons.Default.ContentCopy,
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
SmallTextButton(
|
||||
text = "浏览器打开",
|
||||
icon = Icons.Default.OpenInBrowser,
|
||||
onClick = {
|
||||
onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.compose.material.icons.filled.VerticalAlignTop
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -55,6 +54,7 @@ import io.legado.app.ui.rss.read.ReadRssActivity
|
||||
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
|
||||
import io.legado.app.ui.rss.source.manage.RssSourceActivity
|
||||
import io.legado.app.ui.rss.subscription.RuleSubActivity
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.SourceIcon
|
||||
import io.legado.app.ui.widget.components.button.TopBarActionButton
|
||||
import io.legado.app.ui.widget.components.divider.PillDivider
|
||||
@@ -63,6 +63,7 @@ import io.legado.app.ui.widget.components.list.ListScaffold
|
||||
import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.openUrl
|
||||
import io.legado.app.utils.startActivity
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -135,12 +136,12 @@ fun RssScreen(
|
||||
dismiss()
|
||||
},
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Settings) },
|
||||
text = { Text("订阅源管理") }
|
||||
text = "订阅源管理"
|
||||
)
|
||||
PillDivider()
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Group) },
|
||||
text = { Text(stringResource(R.string.all)) },
|
||||
text = stringResource(R.string.all),
|
||||
onClick = {
|
||||
viewModel.setGroup("")
|
||||
dismiss()
|
||||
@@ -149,7 +150,7 @@ fun RssScreen(
|
||||
uiState.groups.forEach { group ->
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Outlined.Label) },
|
||||
text = { Text(group) },
|
||||
text = group,
|
||||
onClick = {
|
||||
viewModel.setGroup(group)
|
||||
dismiss()
|
||||
@@ -188,8 +189,8 @@ fun RssScreen(
|
||||
sourceToDelete?.let { source ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { sourceToDelete = null },
|
||||
title = { Text(stringResource(R.string.draw)) },
|
||||
text = { Text(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
|
||||
title = { AppText(stringResource(R.string.draw)) },
|
||||
text = { AppText(stringResource(R.string.sure_del) + "\n" + source.sourceName) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
@@ -197,12 +198,12 @@ fun RssScreen(
|
||||
sourceToDelete = null
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.yes))
|
||||
AppText(stringResource(R.string.yes))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { sourceToDelete = null }) {
|
||||
Text(stringResource(R.string.no))
|
||||
AppText(stringResource(R.string.no))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -246,7 +247,7 @@ fun RssSourceGridItem(
|
||||
PillHeaderDivider(title = source.sourceName)
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.VerticalAlignTop) },
|
||||
text = { Text(stringResource(R.string.to_top)) },
|
||||
text = stringResource(R.string.to_top),
|
||||
onClick = {
|
||||
onTop()
|
||||
showMenu = false
|
||||
@@ -254,7 +255,7 @@ fun RssSourceGridItem(
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Edit) },
|
||||
text = { Text(stringResource(R.string.edit)) },
|
||||
text = stringResource(R.string.edit),
|
||||
onClick = {
|
||||
onEdit()
|
||||
showMenu = false
|
||||
@@ -263,7 +264,7 @@ fun RssSourceGridItem(
|
||||
if (!source.loginUrl.isNullOrBlank()) {
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
|
||||
text = { Text(stringResource(R.string.login)) },
|
||||
text = stringResource(R.string.login),
|
||||
onClick = {
|
||||
onLogin()
|
||||
showMenu = false
|
||||
@@ -272,7 +273,7 @@ fun RssSourceGridItem(
|
||||
}
|
||||
RoundDropdownMenuItem(
|
||||
leadingIcon = { MenuItemIcon(Icons.Default.Close) },
|
||||
text = { Text(stringResource(R.string.disable_source)) },
|
||||
text = stringResource(R.string.disable_source),
|
||||
onClick = {
|
||||
onDisable()
|
||||
showMenu = false
|
||||
@@ -285,12 +286,8 @@ fun RssSourceGridItem(
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
stringResource(R.string.delete),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
},
|
||||
text = stringResource(R.string.delete),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
onClick = {
|
||||
onDelete()
|
||||
showMenu = false
|
||||
@@ -299,9 +296,9 @@ fun RssSourceGridItem(
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = source.sourceName,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center,
|
||||
|
||||
@@ -8,11 +8,9 @@ import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
@@ -20,20 +18,15 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.FileOpen
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LoadingIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
@@ -50,14 +43,12 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@@ -76,6 +67,8 @@ import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
||||
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
||||
import io.legado.app.ui.widget.components.tabRow.AppTabRow
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
@@ -165,56 +158,48 @@ fun ReplaceRuleScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showImportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_replace_rule),
|
||||
onSelectSysFile = { types ->
|
||||
importDoc.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showUrlInput = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showImportSheet,
|
||||
onDismissRequest = { showImportSheet = false },
|
||||
title = stringResource(R.string.import_replace_rule),
|
||||
onSelectSysFile = { types ->
|
||||
importDoc.launch(types)
|
||||
showImportSheet = false
|
||||
},
|
||||
onManualInput = {
|
||||
showUrlInput = true
|
||||
showImportSheet = false
|
||||
},
|
||||
allowExtensions = arrayOf("json", "txt")
|
||||
)
|
||||
|
||||
if (showExportSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportDoc.launch("exportReplaceRule.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showExportSheet,
|
||||
onDismissRequest = { showExportSheet = false },
|
||||
onSelectSysDir = {
|
||||
showExportSheet = false
|
||||
exportDoc.launch("exportReplaceRule.json")
|
||||
},
|
||||
onUpload = {
|
||||
showExportSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
|
||||
(importState as? BaseImportUiState.Success<ReplaceRule>)?.let { state ->
|
||||
BatchImportDialog(
|
||||
title = stringResource(R.string.import_replace_rule),
|
||||
importState = state,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
topBarActions = {},
|
||||
itemContent = { rule, _ ->
|
||||
Column {
|
||||
Text(rule.name, style = MaterialTheme.typography.titleMedium)
|
||||
if (!rule.group.isNullOrBlank()) {
|
||||
Text(rule.group!!, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
BatchImportDialog(
|
||||
title = stringResource(R.string.import_replace_rule),
|
||||
importState = importState,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
topBarActions = {},
|
||||
itemTitle = { rule -> rule.name },
|
||||
itemSubtitle = { rule ->
|
||||
rule.group?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
)
|
||||
|
||||
if (importState is BaseImportUiState.Loading) {
|
||||
Dialog(onDismissRequest = { viewModel.cancelImport() }) { LoadingIndicator() }
|
||||
@@ -267,28 +252,29 @@ fun ReplaceRuleScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showGroupManageSheet) {
|
||||
GroupManageBottomSheet(
|
||||
groups = groups,
|
||||
onDismissRequest = { showGroupManageSheet = false },
|
||||
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
|
||||
onDeleteGroup = { viewModel.delGroup(it) }
|
||||
)
|
||||
}
|
||||
|
||||
GroupManageBottomSheet(
|
||||
show = showGroupManageSheet,
|
||||
groups = groups,
|
||||
onDismissRequest = { showGroupManageSheet = false },
|
||||
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
|
||||
onDeleteGroup = { viewModel.delGroup(it) }
|
||||
)
|
||||
|
||||
|
||||
showDeleteRuleDialog?.let { rule ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteRuleDialog = null },
|
||||
title = { Text(stringResource(R.string.delete)) },
|
||||
text = { Text(stringResource(R.string.del_msg)) },
|
||||
title = { AppText(stringResource(R.string.delete)) },
|
||||
text = { AppText(stringResource(R.string.del_msg)) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.delete(rule); showDeleteRuleDialog = null
|
||||
}) { Text(stringResource(R.string.ok)) }
|
||||
}) { AppText(stringResource(R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteRuleDialog = null }) {
|
||||
Text(
|
||||
AppText(
|
||||
stringResource(R.string.cancel)
|
||||
)
|
||||
}
|
||||
@@ -347,39 +333,21 @@ fun ReplaceRuleScreen(
|
||||
},
|
||||
bottomContent = {
|
||||
if (tabItems.size > 1) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = selectedTabIndex.coerceAtMost(tabItems.size - 1)
|
||||
.coerceAtLeast(0),
|
||||
edgePadding = 0.dp,
|
||||
divider = {},
|
||||
containerColor = Color.Transparent,
|
||||
) {
|
||||
tabItems.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = selectedTabIndex == index,
|
||||
onClick = {
|
||||
selectedTabIndex = index
|
||||
viewModel.setGroup(title)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
)
|
||||
AppTabRow(
|
||||
tabTitles = tabItems,
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
onTabSelected = { index ->
|
||||
selectedTabIndex = index
|
||||
viewModel.setGroup(tabItems[index])
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
TooltipBox(
|
||||
positionProvider =
|
||||
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = { PlainTooltip { Text("Localized description") } },
|
||||
tooltip = { PlainTooltip { AppText("Localized description") } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
with(sharedTransitionScope) {
|
||||
@@ -406,29 +374,29 @@ fun ReplaceRuleScreen(
|
||||
snackbarHostState = snackbarHostState,
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_str)) },
|
||||
text = stringResource(R.string.import_str),
|
||||
onClick = { showImportSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.FileOpen, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("分组管理") },
|
||||
text = "分组管理",
|
||||
onClick = { showGroupManageSheet = true; dismiss() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("帮助") },
|
||||
text = "帮助",
|
||||
onClick = { /*TODO*/ dismiss() }
|
||||
)
|
||||
PillDivider()
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("旧的在前") },
|
||||
text = "旧的在前",
|
||||
onClick = { viewModel.setSortMode("asc"); dismiss() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("新的在前") },
|
||||
text = "新的在前",
|
||||
onClick = { viewModel.setSortMode("desc"); dismiss() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("名称升序") },
|
||||
text = "名称升序",
|
||||
onClick = {
|
||||
viewModel.setSortMode("name_asc")
|
||||
dismiss()
|
||||
@@ -438,7 +406,7 @@ fun ReplaceRuleScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text("名称降序") },
|
||||
text = "名称降序",
|
||||
onClick = {
|
||||
viewModel.setSortMode("name_desc")
|
||||
dismiss()
|
||||
@@ -495,16 +463,16 @@ fun ReplaceRuleScreen(
|
||||
//clipInOverlayDuringTransition = OverlayClip(RoundedCornerShape(12.dp))
|
||||
),
|
||||
dropdownContent = { dismiss ->
|
||||
DropdownMenuItem(
|
||||
text = { Text("移至顶部") },
|
||||
RoundDropdownMenuItem(
|
||||
text = "移至顶部",
|
||||
onClick = { viewModel.toTop(ui.rule); dismiss() }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("移至底部") },
|
||||
RoundDropdownMenuItem(
|
||||
text = "移至底部",
|
||||
onClick = { viewModel.toBottom(ui.rule); dismiss() }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("删除") },
|
||||
RoundDropdownMenuItem(
|
||||
text = "删除",
|
||||
onClick = { showDeleteRuleDialog = ui.rule; dismiss() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
@@ -78,10 +77,11 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.button.AlertButton
|
||||
import io.legado.app.ui.widget.components.button.TopbarNavigationButton
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
@@ -119,7 +119,7 @@ fun ReplaceEditScreen(
|
||||
.imePadding(),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = { Text(if (state.id > 0) "编辑替换规则" else "新增替换规则") },
|
||||
title = if (state.id > 0) "编辑替换规则" else "新增替换规则",
|
||||
navigationIcon = {
|
||||
TopbarNavigationButton(onClick = onBack)
|
||||
},
|
||||
@@ -143,14 +143,14 @@ fun ReplaceEditScreen(
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("复制规则") },
|
||||
text = { AppText("复制规则") },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
viewModel.copyRule()
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("粘贴规则") },
|
||||
text = { AppText("粘贴规则") },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
viewModel.pasteRule(onSuccess = {})
|
||||
@@ -167,7 +167,7 @@ fun ReplaceEditScreen(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
|
||||
TooltipAnchorPosition.Above
|
||||
),
|
||||
tooltip = { PlainTooltip { Text("添加") } },
|
||||
tooltip = { PlainTooltip { AppText("添加") } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
@@ -214,7 +214,7 @@ fun ReplaceEditScreen(
|
||||
OutlinedTextField(
|
||||
value = state.name,
|
||||
onValueChange = viewModel::onNameChange,
|
||||
label = { Text("规则名称") },
|
||||
label = { AppText("规则名称") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
@@ -234,8 +234,8 @@ fun ReplaceEditScreen(
|
||||
OutlinedTextField(
|
||||
value = state.pattern,
|
||||
onValueChange = viewModel::onPatternChange,
|
||||
label = { Text("匹配规则") },
|
||||
placeholder = { Text("输入正则表达式或关键字") },
|
||||
label = { AppText("匹配规则") },
|
||||
placeholder = { AppText("输入正则表达式或关键字") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
@@ -247,8 +247,8 @@ fun ReplaceEditScreen(
|
||||
OutlinedTextField(
|
||||
value = state.replacement,
|
||||
onValueChange = viewModel::onReplacementChange,
|
||||
label = { Text("替换为") },
|
||||
placeholder = { Text("输入替换内容或捕获组") },
|
||||
label = { AppText("替换为") },
|
||||
placeholder = { AppText("输入替换内容或捕获组") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
@@ -265,7 +265,7 @@ fun ReplaceEditScreen(
|
||||
FilterChip(
|
||||
selected = state.scopeTitle,
|
||||
onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) },
|
||||
label = { Text("标题") },
|
||||
label = { AppText("标题") },
|
||||
leadingIcon = if (state.scopeTitle) {
|
||||
{
|
||||
Icon(
|
||||
@@ -282,7 +282,7 @@ fun ReplaceEditScreen(
|
||||
FilterChip(
|
||||
selected = state.scopeContent,
|
||||
onClick = { viewModel.onScopeContentChange(!state.scopeContent) },
|
||||
label = { Text("内容") },
|
||||
label = { AppText("内容") },
|
||||
leadingIcon = if (state.scopeContent) {
|
||||
{
|
||||
Icon(
|
||||
@@ -299,7 +299,7 @@ fun ReplaceEditScreen(
|
||||
FilterChip(
|
||||
selected = state.isRegex,
|
||||
onClick = { viewModel.onRegexChange(!state.isRegex) },
|
||||
label = { Text("使用正则") },
|
||||
label = { AppText("使用正则") },
|
||||
leadingIcon = if (state.isRegex) {
|
||||
{
|
||||
Icon(
|
||||
@@ -316,8 +316,8 @@ fun ReplaceEditScreen(
|
||||
OutlinedTextField(
|
||||
value = state.scope,
|
||||
onValueChange = viewModel::onScopeChange,
|
||||
label = { Text("特定范围") },
|
||||
placeholder = { Text("指定规则适用的范围") },
|
||||
label = { AppText("特定范围") },
|
||||
placeholder = { AppText("指定规则适用的范围") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
@@ -329,8 +329,8 @@ fun ReplaceEditScreen(
|
||||
OutlinedTextField(
|
||||
value = state.excludeScope,
|
||||
onValueChange = viewModel::onExcludeScopeChange,
|
||||
label = { Text("排除范围") },
|
||||
placeholder = { Text("指定规则不适用的范围") },
|
||||
label = { AppText("排除范围") },
|
||||
placeholder = { AppText("指定规则不适用的范围") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
@@ -342,8 +342,8 @@ fun ReplaceEditScreen(
|
||||
OutlinedTextField(
|
||||
value = state.timeout,
|
||||
onValueChange = viewModel::onTimeoutChange,
|
||||
label = { Text("超时 (ms)") },
|
||||
placeholder = { Text("3000") },
|
||||
label = { AppText("超时 (ms)") },
|
||||
placeholder = { AppText("3000") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
@@ -383,8 +383,8 @@ fun GroupSelector(
|
||||
OutlinedTextField(
|
||||
value = currentGroup,
|
||||
onValueChange = onGroupChange,
|
||||
label = { Text("分组") },
|
||||
placeholder = { Text("默认") },
|
||||
label = { AppText("分组") },
|
||||
placeholder = { AppText("默认") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
||||
modifier = Modifier
|
||||
@@ -400,7 +400,7 @@ fun GroupSelector(
|
||||
) {
|
||||
allGroups.forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(selectionOption) },
|
||||
text = { AppText(selectionOption) },
|
||||
onClick = {
|
||||
onGroupChange(selectionOption)
|
||||
expanded = false
|
||||
@@ -426,9 +426,9 @@ fun ManageGroupDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("分组管理") },
|
||||
title = { AppText("分组管理") },
|
||||
text = {
|
||||
if (groups.isEmpty()) Text("暂无其他分组")
|
||||
if (groups.isEmpty()) AppText("暂无其他分组")
|
||||
else Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
@@ -448,7 +448,7 @@ fun ManageGroupDialog(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(checked = isSelected, onCheckedChange = null)
|
||||
Text(group, Modifier.padding(start = 8.dp))
|
||||
AppText(group, Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -462,7 +462,7 @@ fun ManageGroupDialog(
|
||||
text = "删除选中"
|
||||
)
|
||||
},
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("关闭") } }
|
||||
dismissButton = { TextButton(onClick = onDismiss) { AppText("关闭") } }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ fun QuickInputBar(
|
||||
symbols.forEach { symbol ->
|
||||
AssistChip(
|
||||
onClick = { onInsert(symbol) },
|
||||
label = { Text(symbol) },
|
||||
label = { AppText(symbol) },
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ package io.legado.app.ui.rss.favorites
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -19,7 +17,6 @@ import androidx.compose.material.icons.filled.Group
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -129,11 +126,10 @@ fun RssFavoritesScreen(
|
||||
topBarActions = {},
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = {
|
||||
text = stringResource(R.string.all),
|
||||
leadingIcon = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Default.Group, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text(stringResource(R.string.all))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
@@ -143,15 +139,14 @@ fun RssFavoritesScreen(
|
||||
)
|
||||
groups.forEach { group ->
|
||||
RoundDropdownMenuItem(
|
||||
text = {
|
||||
text = group,
|
||||
leadingIcon = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Outlined.Label,
|
||||
null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text(group)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
@@ -162,11 +157,10 @@ fun RssFavoritesScreen(
|
||||
}
|
||||
PillDivider()
|
||||
RoundDropdownMenuItem(
|
||||
text = {
|
||||
text = stringResource(R.string.delete_select_group),
|
||||
leadingIcon = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Default.DeleteSweep, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text(stringResource(R.string.delete_select_group))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
@@ -175,11 +169,10 @@ fun RssFavoritesScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = {
|
||||
text = stringResource(R.string.all),
|
||||
leadingIcon = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Default.DeleteForever, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text(stringResource(R.string.all))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
@@ -248,14 +241,14 @@ fun RssFavoritesScreen(
|
||||
},
|
||||
dropdownContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.change_group)) },
|
||||
text = stringResource(R.string.change_group),
|
||||
onClick = {
|
||||
showSetGroupDialog = rssStar
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.delete)) },
|
||||
text = stringResource(R.string.delete),
|
||||
onClick = {
|
||||
viewModel.deleteStar(rssStar)
|
||||
dismiss()
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -17,11 +16,9 @@ import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -51,13 +48,13 @@ import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||
import io.legado.app.ui.widget.components.dialog.TextListInputDialog
|
||||
import io.legado.app.ui.widget.components.divider.PillDivider
|
||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
||||
import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
|
||||
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
||||
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
|
||||
@@ -175,46 +172,41 @@ fun RssSourceScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showGroupManageSheet) {
|
||||
GroupManageBottomSheet(
|
||||
groups = groups,
|
||||
onDismissRequest = { showGroupManageSheet = false },
|
||||
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
|
||||
onDeleteGroup = { viewModel.delGroup(it) }
|
||||
)
|
||||
}
|
||||
GroupManageBottomSheet(
|
||||
show = showGroupManageSheet,
|
||||
groups = groups,
|
||||
onDismissRequest = { showGroupManageSheet = false },
|
||||
onUpdateGroup = { old, new -> viewModel.upGroup(old, new) },
|
||||
onDeleteGroup = { viewModel.delGroup(it) }
|
||||
)
|
||||
|
||||
if (showFilePickerSheet) {
|
||||
FilePickerSheet(
|
||||
onDismissRequest = { showFilePickerSheet = false },
|
||||
onSelectSysDir = {
|
||||
showFilePickerSheet = false
|
||||
exportDoc.launch("exportRssSource.json")
|
||||
},
|
||||
onUpload = {
|
||||
showFilePickerSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
}
|
||||
|
||||
(importState as? BaseImportUiState.Success<RssSource>)?.let { state ->
|
||||
BatchImportDialog(
|
||||
title = stringResource(R.string.import_rss_source),
|
||||
importState = state,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
itemContent = { source, _ ->
|
||||
Column {
|
||||
Text(source.sourceName, style = MaterialTheme.typography.titleMedium)
|
||||
Text(source.sourceUrl, style = MaterialTheme.typography.bodySmall, maxLines = 1)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
FilePickerSheet(
|
||||
show = showFilePickerSheet,
|
||||
onDismissRequest = { showFilePickerSheet = false },
|
||||
onSelectSysDir = {
|
||||
showFilePickerSheet = false
|
||||
exportDoc.launch("exportRssSource.json")
|
||||
},
|
||||
onUpload = {
|
||||
showFilePickerSheet = false
|
||||
viewModel.uploadSelectedRules(selectedIds, rules)
|
||||
},
|
||||
allowExtensions = arrayOf("json")
|
||||
)
|
||||
|
||||
BatchImportDialog(
|
||||
title = stringResource(R.string.import_rss_source),
|
||||
importState = importState,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
onToggleAll = { viewModel.toggleImportAll(it) },
|
||||
onConfirm = { viewModel.saveImportedRules() },
|
||||
itemTitle = { rule -> rule.sourceName },
|
||||
itemSubtitle = { rule ->
|
||||
rule.sourceUrl.takeIf { it.isNotBlank() }
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(reorderableState.isAnyItemDragging) {
|
||||
if (!reorderableState.isAnyItemDragging) {
|
||||
@@ -225,16 +217,16 @@ fun RssSourceScreen(
|
||||
showDeleteRuleDialog?.let { source ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteRuleDialog = null },
|
||||
title = { Text(stringResource(R.string.delete)) },
|
||||
text = { Text(stringResource(R.string.del_msg)) },
|
||||
title = { AppText(stringResource(R.string.delete)) },
|
||||
text = { AppText(stringResource(R.string.del_msg)) },
|
||||
confirmButton = {
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.del(source); showDeleteRuleDialog = null
|
||||
}) { Text(stringResource(R.string.ok)) }
|
||||
}) { AppText(stringResource(R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteRuleDialog = null }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -287,11 +279,11 @@ fun RssSourceScreen(
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
onClick = { showGroupManageSheet = true },
|
||||
text = { Text("分组管理") },
|
||||
text = "分组管理",
|
||||
)
|
||||
Box {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_rss_source)) },
|
||||
text = stringResource(R.string.import_rss_source),
|
||||
onClick = { showImportMenu = true }
|
||||
)
|
||||
RoundDropdownMenu(
|
||||
@@ -299,7 +291,7 @@ fun RssSourceScreen(
|
||||
onDismissRequest = { showImportMenu = false }
|
||||
) {
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_on_line)) },
|
||||
text = stringResource(R.string.import_on_line),
|
||||
onClick = {
|
||||
showImportMenu = false
|
||||
dismiss()
|
||||
@@ -307,7 +299,7 @@ fun RssSourceScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_local)) },
|
||||
text = stringResource(R.string.import_local),
|
||||
onClick = {
|
||||
showImportMenu = false
|
||||
dismiss()
|
||||
@@ -315,7 +307,7 @@ fun RssSourceScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.import_default_rule)) },
|
||||
text = stringResource(R.string.import_default_rule),
|
||||
onClick = {
|
||||
showImportMenu = false
|
||||
dismiss()
|
||||
@@ -326,29 +318,29 @@ fun RssSourceScreen(
|
||||
}
|
||||
PillDivider()
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.all)) },
|
||||
text = stringResource(R.string.all),
|
||||
onClick = { dismiss(); viewModel.setGroupFilter(null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.enabled)) },
|
||||
text = stringResource(R.string.enabled),
|
||||
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_ENABLED) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.disabled)) },
|
||||
text = stringResource(R.string.disabled),
|
||||
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_DISABLED) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.need_login)) },
|
||||
text = stringResource(R.string.need_login),
|
||||
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_LOGIN) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.no_group)) },
|
||||
text = stringResource(R.string.no_group),
|
||||
onClick = { dismiss(); viewModel.setGroupFilter(RssSourceViewModel.FILTER_NO_GROUP) }
|
||||
)
|
||||
PillDivider()
|
||||
groups.forEach { group ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(group) },
|
||||
text = group,
|
||||
onClick = { dismiss(); viewModel.setGroupFilter("${RssSourceViewModel.PREFIX_GROUP}$group") }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,9 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
@@ -54,12 +52,14 @@ import io.legado.app.data.entities.RuleSubType
|
||||
import io.legado.app.ui.association.ImportBookSourceDialog
|
||||
import io.legado.app.ui.association.ImportReplaceRuleDialog
|
||||
import io.legado.app.ui.association.ImportRssSourceDialog
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||
import io.legado.app.ui.widget.components.card.SelectionItemCard
|
||||
import io.legado.app.ui.widget.components.checkBox.CheckboxGroupContainer
|
||||
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||
import io.legado.app.ui.widget.components.rules.RuleListScaffold
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.toastOnUi
|
||||
|
||||
@@ -89,11 +89,12 @@ fun RuleSubScreen(
|
||||
topBarActions = {},
|
||||
dropDownMenuContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = {
|
||||
text = stringResource(R.string.sort),
|
||||
leadingIcon = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.AutoMirrored.Filled.Sort, null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.size(12.dp))
|
||||
Text(stringResource(R.string.sort))
|
||||
AppText(stringResource(R.string.sort))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
@@ -106,7 +107,7 @@ fun RuleSubScreen(
|
||||
TooltipBox(
|
||||
positionProvider =
|
||||
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = { PlainTooltip { Text("Localized description") } },
|
||||
tooltip = { PlainTooltip { AppText("Localized description") } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
@@ -185,7 +186,7 @@ fun RuleSubScreen(
|
||||
},
|
||||
dropdownContent = { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.delete)) },
|
||||
text = stringResource(R.string.delete),
|
||||
onClick = {
|
||||
viewModel.delete(ruleSub)
|
||||
dismiss()
|
||||
@@ -226,24 +227,24 @@ fun RuleSubEditDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.rule_subscription)) },
|
||||
title = { AppText(stringResource(R.string.rule_subscription)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text(stringResource(R.string.name)) },
|
||||
label = { AppText(stringResource(R.string.name)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = url,
|
||||
onValueChange = { url = it },
|
||||
label = { Text("URL") },
|
||||
label = { AppText("URL") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = "订阅类型",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
style = LegadoTheme.typography.titleSmall,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
CheckboxGroupContainer(columns = 2) {
|
||||
@@ -265,12 +266,12 @@ fun RuleSubEditDialog(
|
||||
TextButton(onClick = {
|
||||
onConfirm(ruleSub.copy(name = name, url = url, type = type))
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.legado.app.ui.theme
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
|
||||
@Composable
|
||||
fun Modifier.adaptiveHorizontalPadding(): Modifier {
|
||||
val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 16.dp
|
||||
return this.padding(horizontal = horizontal)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Modifier.adaptiveVerticalPadding(): Modifier {
|
||||
val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp else 8.dp
|
||||
return this.padding(horizontal = horizontal)
|
||||
}
|
||||
@@ -7,9 +7,14 @@ import androidx.compose.material3.MotionScheme
|
||||
import androidx.compose.material3.Shapes
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.theme.ThemeController
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -20,29 +25,274 @@ fun AppTheme(
|
||||
val context = LocalContext.current
|
||||
val appThemeMode = ThemeResolver.resolveThemeMode(ThemeConfig.appTheme)
|
||||
val isPureBlack = ThemeConfig.isPureBlack
|
||||
val paletteStyle = ThemeConfig.paletteStyle
|
||||
val paletteStyleStr = ThemeConfig.paletteStyle
|
||||
val materialVersion = ThemeConfig.materialVersion
|
||||
val composeEngine = ThemeConfig.composeEngine
|
||||
val colorSchemeMode = ThemeResolver.resolveColorSchemeMode(ThemeConfig.themeMode)
|
||||
val paletteStyle =
|
||||
remember(paletteStyleStr) { ThemeResolver.resolvePaletteStyle(paletteStyleStr) }
|
||||
val seedColor = remember(ThemeConfig.cPrimary) {
|
||||
if (ThemeConfig.cPrimary != 0) Color(ThemeConfig.cPrimary) else Color(0xFF3482FF)
|
||||
}
|
||||
|
||||
val colorScheme =
|
||||
remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyle, materialVersion) {
|
||||
remember(context, appThemeMode, darkTheme, isPureBlack, paletteStyleStr, materialVersion) {
|
||||
ThemeManager.getColorScheme(
|
||||
context = context,
|
||||
mode = appThemeMode,
|
||||
darkTheme = darkTheme,
|
||||
isAmoled = isPureBlack,
|
||||
paletteStyle = paletteStyle,
|
||||
paletteStyle = paletteStyleStr,
|
||||
materialVersion = materialVersion
|
||||
)
|
||||
}
|
||||
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography(),
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
shapes = Shapes()
|
||||
val themeColors = remember(
|
||||
colorScheme,
|
||||
darkTheme,
|
||||
seedColor,
|
||||
paletteStyle,
|
||||
colorSchemeMode,
|
||||
composeEngine
|
||||
) {
|
||||
AppBackground(darkTheme = darkTheme) {
|
||||
content()
|
||||
LegadoThemeMode(
|
||||
colorScheme = colorScheme,
|
||||
isDark = darkTheme,
|
||||
seedColor = seedColor,
|
||||
paletteStyle = paletteStyle,
|
||||
themeMode = colorSchemeMode,
|
||||
useDynamicColor = appThemeMode == AppThemeMode.Dynamic,
|
||||
composeEngine = composeEngine
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalLegadoThemeColors provides themeColors
|
||||
) {
|
||||
if (ThemeResolver.isMiuixEngine(themeColors.composeEngine)) {
|
||||
val controller = remember(colorSchemeMode, darkTheme) {
|
||||
ThemeController(colorSchemeMode = colorSchemeMode, isDark = darkTheme)
|
||||
}
|
||||
|
||||
MiuixTheme(controller = controller) {
|
||||
val miuixStyles = MiuixTheme.textStyles
|
||||
val legadoTypography = remember(miuixStyles) {
|
||||
LegadoTypography(
|
||||
headlineLarge = miuixStyles.title1,
|
||||
headlineLargeEmphasized = miuixStyles.title1.copy(fontWeight = FontWeight.Medium),
|
||||
headlineMedium = miuixStyles.title2,
|
||||
headlineMediumEmphasized = miuixStyles.title2.copy(fontWeight = FontWeight.Medium),
|
||||
headlineSmall = miuixStyles.title3,
|
||||
headlineSmallEmphasized = miuixStyles.title3.copy(fontWeight = FontWeight.Medium),
|
||||
|
||||
|
||||
titleLarge = miuixStyles.headline1,
|
||||
titleLargeEmphasized = miuixStyles.headline1.copy(fontWeight = FontWeight.Medium),
|
||||
titleMedium = miuixStyles.headline2,
|
||||
titleMediumEmphasized = miuixStyles.headline2.copy(fontWeight = FontWeight.Medium),
|
||||
titleSmall = miuixStyles.subtitle,
|
||||
titleSmallEmphasized = miuixStyles.subtitle.copy(fontWeight = FontWeight.Medium),
|
||||
|
||||
|
||||
bodyLarge = miuixStyles.paragraph,
|
||||
bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
|
||||
bodyMedium = miuixStyles.body1,
|
||||
bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
|
||||
bodySmall = miuixStyles.body2,
|
||||
bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium),
|
||||
|
||||
labelLarge = miuixStyles.button,
|
||||
labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium),
|
||||
labelMedium = miuixStyles.footnote1,
|
||||
labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
|
||||
labelSmall = miuixStyles.footnote2,
|
||||
labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
|
||||
)
|
||||
}
|
||||
|
||||
val miuixColorScheme = MiuixTheme.colorScheme
|
||||
|
||||
val mappedColorScheme = remember(miuixColorScheme) {
|
||||
LegadoColorScheme(
|
||||
primary = miuixColorScheme.primary,
|
||||
onPrimary = miuixColorScheme.onPrimary,
|
||||
primaryContainer = miuixColorScheme.primaryContainer,
|
||||
onPrimaryContainer = miuixColorScheme.onPrimaryContainer,
|
||||
inversePrimary = miuixColorScheme.primaryVariant,
|
||||
|
||||
secondary = miuixColorScheme.primary.copy(alpha = 0.5f),
|
||||
onSecondary = miuixColorScheme.onPrimary.copy(alpha = 0.5f),
|
||||
secondaryContainer = miuixColorScheme.primaryContainer.copy(alpha = 0.5f),
|
||||
onSecondaryContainer = miuixColorScheme.onPrimaryContainer.copy(alpha = 0.5f),
|
||||
|
||||
tertiary = miuixColorScheme.secondary,
|
||||
onTertiary = miuixColorScheme.onSecondary,
|
||||
tertiaryContainer = miuixColorScheme.secondaryContainer,
|
||||
onTertiaryContainer = miuixColorScheme.onSecondaryContainer,
|
||||
|
||||
// ================= 4. 背景与表面 (Background & Surface) =================
|
||||
background = miuixColorScheme.background,
|
||||
onBackground = miuixColorScheme.onBackground,
|
||||
|
||||
surface = miuixColorScheme.surface,
|
||||
onSurface = miuixColorScheme.onSurface,
|
||||
surfaceVariant = miuixColorScheme.surfaceVariant,
|
||||
// M3 的 onSurfaceVariant 通常是次级文字色。Miuix 的 onSurfaceSecondary 完美契合这个语义
|
||||
onSurfaceVariant = miuixColorScheme.onSurfaceSecondary,
|
||||
|
||||
// M3 中用于给 Surface 叠加一层极淡主题色的属性,通常直接取 primary
|
||||
surfaceTint = miuixColorScheme.primary,
|
||||
|
||||
// Inverse 系列通常用于深色模式下的反色提示(如 Snackbar)。
|
||||
// 简单映射法:直接用现有的 onSurface 和 surface 交叉互换。
|
||||
inverseSurface = miuixColorScheme.onSurface,
|
||||
inverseOnSurface = miuixColorScheme.surface,
|
||||
|
||||
// ================= 5. 错误状态 (Error) =================
|
||||
error = miuixColorScheme.error,
|
||||
onError = miuixColorScheme.onError,
|
||||
errorContainer = miuixColorScheme.errorContainer,
|
||||
onErrorContainer = miuixColorScheme.onErrorContainer,
|
||||
|
||||
// ================= 6. 边框、分割线与遮罩 (Outline & Scrim) =================
|
||||
outline = miuixColorScheme.outline,
|
||||
// outlineVariant 在 M3 中常用于分割线。Miuix 刚好有 dividerLine
|
||||
outlineVariant = miuixColorScheme.dividerLine,
|
||||
// scrim 是 M3 的遮罩层(如弹窗背后的阴影)。Miuix 刚好有 windowDimming
|
||||
scrim = miuixColorScheme.windowDimming,
|
||||
|
||||
surfaceBright = miuixColorScheme.surface, // Miuix 缺省,用 surface 兜底
|
||||
surfaceDim = miuixColorScheme.background, // Miuix 缺省,用 background 兜底
|
||||
surfaceContainer = miuixColorScheme.surfaceContainer,
|
||||
surfaceContainerHigh = miuixColorScheme.surfaceContainerHigh,
|
||||
surfaceContainerHighest = miuixColorScheme.surfaceContainerHighest,
|
||||
surfaceContainerLow = miuixColorScheme.secondaryContainer,
|
||||
surfaceContainerLowest = miuixColorScheme.background,
|
||||
|
||||
primaryFixed = miuixColorScheme.primaryContainer,
|
||||
primaryFixedDim = miuixColorScheme.primary,
|
||||
onPrimaryFixed = miuixColorScheme.onPrimaryContainer,
|
||||
onPrimaryFixedVariant = miuixColorScheme.onPrimary,
|
||||
secondaryFixed = miuixColorScheme.secondaryContainer,
|
||||
secondaryFixedDim = miuixColorScheme.secondary,
|
||||
onSecondaryFixed = miuixColorScheme.onSecondaryContainer,
|
||||
onSecondaryFixedVariant = miuixColorScheme.onSecondary,
|
||||
tertiaryFixed = miuixColorScheme.tertiaryContainer,
|
||||
tertiaryFixedDim = miuixColorScheme.tertiaryContainerVariant,
|
||||
onTertiaryFixed = miuixColorScheme.onTertiaryContainer,
|
||||
onTertiaryFixedVariant = miuixColorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalLegadoTypography provides legadoTypography,
|
||||
LocalLegadoColorScheme provides mappedColorScheme
|
||||
) {
|
||||
AppBackground(darkTheme = darkTheme) { content() }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val Typography = Typography()
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
shapes = Shapes()
|
||||
) {
|
||||
val legadoTypography = remember(Typography) {
|
||||
LegadoTypography(
|
||||
headlineLarge = Typography.headlineLarge,
|
||||
headlineLargeEmphasized = Typography.headlineLargeEmphasized,
|
||||
headlineMedium = Typography.headlineMedium,
|
||||
headlineMediumEmphasized = Typography.headlineMediumEmphasized,
|
||||
headlineSmall = Typography.headlineSmall,
|
||||
headlineSmallEmphasized = Typography.headlineSmallEmphasized,
|
||||
|
||||
|
||||
titleLarge = Typography.titleLarge,
|
||||
titleLargeEmphasized = Typography.titleLargeEmphasized,
|
||||
titleMedium = Typography.titleMedium,
|
||||
titleMediumEmphasized = Typography.titleMediumEmphasized,
|
||||
titleSmall = Typography.titleSmall,
|
||||
titleSmallEmphasized = Typography.titleSmallEmphasized,
|
||||
|
||||
|
||||
bodyLarge = Typography.bodyLarge,
|
||||
bodyLargeEmphasized = Typography.bodyLargeEmphasized,
|
||||
bodyMedium = Typography.bodyMedium,
|
||||
bodyMediumEmphasized = Typography.bodyMediumEmphasized,
|
||||
bodySmall = Typography.bodySmall,
|
||||
bodySmallEmphasized = Typography.bodySmallEmphasized,
|
||||
|
||||
labelLarge = Typography.labelLarge,
|
||||
labelLargeEmphasized = Typography.labelLargeEmphasized,
|
||||
labelMedium = Typography.labelMedium,
|
||||
labelMediumEmphasized = Typography.labelMediumEmphasized,
|
||||
labelSmall = Typography.labelSmall,
|
||||
labelSmallEmphasized = Typography.labelSmallEmphasized
|
||||
)
|
||||
}
|
||||
|
||||
val semanticColors = remember(colorScheme) {
|
||||
LegadoColorScheme(
|
||||
primary = colorScheme.primary,
|
||||
onPrimary = colorScheme.onPrimary,
|
||||
primaryContainer = colorScheme.primaryContainer,
|
||||
onPrimaryContainer = colorScheme.onPrimaryContainer,
|
||||
inversePrimary = colorScheme.inversePrimary,
|
||||
secondary = colorScheme.secondary,
|
||||
onSecondary = colorScheme.onSecondary,
|
||||
secondaryContainer = colorScheme.secondaryContainer,
|
||||
onSecondaryContainer = colorScheme.onSecondaryContainer,
|
||||
tertiary = colorScheme.tertiary,
|
||||
onTertiary = colorScheme.onTertiary,
|
||||
tertiaryContainer = colorScheme.tertiaryContainer,
|
||||
onTertiaryContainer = colorScheme.onTertiaryContainer,
|
||||
background = colorScheme.background,
|
||||
onBackground = colorScheme.onBackground,
|
||||
surface = colorScheme.surface,
|
||||
onSurface = colorScheme.onSurface,
|
||||
surfaceVariant = colorScheme.surfaceVariant,
|
||||
onSurfaceVariant = colorScheme.onSurfaceVariant,
|
||||
surfaceTint = colorScheme.surfaceTint,
|
||||
inverseSurface = colorScheme.inverseSurface,
|
||||
inverseOnSurface = colorScheme.inverseOnSurface,
|
||||
error = colorScheme.error,
|
||||
onError = colorScheme.onError,
|
||||
errorContainer = colorScheme.errorContainer,
|
||||
onErrorContainer = colorScheme.onErrorContainer,
|
||||
outline = colorScheme.outline,
|
||||
outlineVariant = colorScheme.outlineVariant,
|
||||
scrim = colorScheme.scrim,
|
||||
surfaceBright = colorScheme.surfaceBright,
|
||||
surfaceDim = colorScheme.surfaceDim,
|
||||
surfaceContainer = colorScheme.surfaceContainer,
|
||||
surfaceContainerHigh = colorScheme.surfaceContainerHigh,
|
||||
surfaceContainerHighest = colorScheme.surfaceContainerHighest,
|
||||
surfaceContainerLow = colorScheme.surfaceContainerLow,
|
||||
surfaceContainerLowest = colorScheme.surfaceContainerLowest,
|
||||
primaryFixed = colorScheme.primaryFixed,
|
||||
primaryFixedDim = colorScheme.primaryFixedDim,
|
||||
onPrimaryFixed = colorScheme.onPrimaryFixed,
|
||||
onPrimaryFixedVariant = colorScheme.onPrimaryFixedVariant,
|
||||
secondaryFixed = colorScheme.secondaryFixed,
|
||||
secondaryFixedDim = colorScheme.secondaryFixedDim,
|
||||
onSecondaryFixed = colorScheme.onSecondaryFixed,
|
||||
onSecondaryFixedVariant = colorScheme.onSecondaryFixedVariant,
|
||||
tertiaryFixed = colorScheme.tertiaryFixed,
|
||||
tertiaryFixedDim = colorScheme.tertiaryFixedDim,
|
||||
onTertiaryFixed = colorScheme.onTertiaryFixed,
|
||||
onTertiaryFixedVariant = colorScheme.onTertiaryFixedVariant
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalLegadoTypography provides legadoTypography,
|
||||
LocalLegadoColorScheme provides semanticColors
|
||||
) {
|
||||
AppBackground(darkTheme = darkTheme) { content() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.theme.hazeStyle.HazeLegado
|
||||
|
||||
/**
|
||||
* 自动感知全局配置的 HazeSource
|
||||
@@ -31,9 +31,9 @@ fun Modifier.responsiveHazeEffect(
|
||||
if (!enableBlur) return this
|
||||
|
||||
val style = if (enableProgressiveBlur) {
|
||||
HazeMaterials.ultraThin()
|
||||
HazeLegado.ultraThin()
|
||||
} else {
|
||||
HazeMaterials.regular()
|
||||
HazeLegado.regular()
|
||||
}
|
||||
|
||||
return this.hazeEffect(
|
||||
@@ -61,6 +61,6 @@ fun Modifier.regularHazeEffect(state: HazeState): Modifier {
|
||||
|
||||
return this.hazeEffect(
|
||||
state = state,
|
||||
style = HazeMaterials.ultraThin()
|
||||
style = HazeLegado.ultraThin()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package io.legado.app.ui.theme
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import com.materialkolor.PaletteStyle
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
|
||||
|
||||
data class LegadoThemeMode(
|
||||
val colorScheme: ColorScheme,
|
||||
val isDark: Boolean,
|
||||
val seedColor: Color,
|
||||
val paletteStyle: PaletteStyle,
|
||||
val themeMode: ColorSchemeMode,
|
||||
val useDynamicColor: Boolean,
|
||||
val composeEngine: String,
|
||||
)
|
||||
|
||||
data class LegadoColorScheme(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val primaryContainer: Color,
|
||||
val onPrimaryContainer: Color,
|
||||
val inversePrimary: Color,
|
||||
val secondary: Color,
|
||||
val onSecondary: Color,
|
||||
val secondaryContainer: Color,
|
||||
val onSecondaryContainer: Color,
|
||||
val tertiary: Color,
|
||||
val onTertiary: Color,
|
||||
val tertiaryContainer: Color,
|
||||
val onTertiaryContainer: Color,
|
||||
val background: Color,
|
||||
val onBackground: Color,
|
||||
val surface: Color,
|
||||
val onSurface: Color,
|
||||
val surfaceVariant: Color,
|
||||
val onSurfaceVariant: Color,
|
||||
val surfaceTint: Color,
|
||||
val inverseSurface: Color,
|
||||
val inverseOnSurface: Color,
|
||||
val error: Color,
|
||||
val onError: Color,
|
||||
val errorContainer: Color,
|
||||
val onErrorContainer: Color,
|
||||
val outline: Color,
|
||||
val outlineVariant: Color,
|
||||
val scrim: Color,
|
||||
val surfaceBright: Color,
|
||||
val surfaceDim: Color,
|
||||
val surfaceContainer: Color,
|
||||
val surfaceContainerHigh: Color,
|
||||
val surfaceContainerHighest: Color,
|
||||
val surfaceContainerLow: Color,
|
||||
val surfaceContainerLowest: Color,
|
||||
val primaryFixed: Color,
|
||||
val primaryFixedDim: Color,
|
||||
val onPrimaryFixed: Color,
|
||||
val onPrimaryFixedVariant: Color,
|
||||
val secondaryFixed: Color,
|
||||
val secondaryFixedDim: Color,
|
||||
val onSecondaryFixed: Color,
|
||||
val onSecondaryFixedVariant: Color,
|
||||
val tertiaryFixed: Color,
|
||||
val tertiaryFixedDim: Color,
|
||||
val onTertiaryFixed: Color,
|
||||
val onTertiaryFixedVariant: Color,
|
||||
)
|
||||
|
||||
data class LegadoTypography(
|
||||
val headlineLarge: TextStyle,
|
||||
val headlineLargeEmphasized: TextStyle,
|
||||
val headlineMedium: TextStyle,
|
||||
val headlineMediumEmphasized: TextStyle,
|
||||
val headlineSmall: TextStyle,
|
||||
val headlineSmallEmphasized: TextStyle,
|
||||
|
||||
val titleLarge: TextStyle,
|
||||
val titleLargeEmphasized: TextStyle,
|
||||
val titleMedium: TextStyle,
|
||||
val titleMediumEmphasized: TextStyle,
|
||||
val titleSmall: TextStyle,
|
||||
val titleSmallEmphasized: TextStyle,
|
||||
|
||||
|
||||
val bodyLarge: TextStyle,
|
||||
val bodyLargeEmphasized: TextStyle,
|
||||
val bodyMedium: TextStyle,
|
||||
val bodyMediumEmphasized: TextStyle,
|
||||
val bodySmall: TextStyle,
|
||||
val bodySmallEmphasized: TextStyle,
|
||||
|
||||
val labelLarge: TextStyle,
|
||||
val labelLargeEmphasized: TextStyle,
|
||||
val labelMedium: TextStyle,
|
||||
val labelMediumEmphasized: TextStyle,
|
||||
val labelSmall: TextStyle,
|
||||
val labelSmallEmphasized: TextStyle,
|
||||
|
||||
)
|
||||
|
||||
val LocalLegadoColorScheme = staticCompositionLocalOf<LegadoColorScheme> {
|
||||
error("No ColorScheme provided")
|
||||
}
|
||||
|
||||
val LocalLegadoTypography = staticCompositionLocalOf<LegadoTypography> {
|
||||
error("No Typography provided")
|
||||
}
|
||||
|
||||
val LocalLegadoThemeColors = staticCompositionLocalOf {
|
||||
LegadoThemeMode(
|
||||
colorScheme = lightColorScheme(),
|
||||
isDark = false,
|
||||
seedColor = Color.Unspecified,
|
||||
paletteStyle = PaletteStyle.TonalSpot,
|
||||
themeMode = ColorSchemeMode.System,
|
||||
useDynamicColor = true,
|
||||
composeEngine = "material"
|
||||
)
|
||||
}
|
||||
|
||||
val LocalHazeState = compositionLocalOf<HazeState?> { null }
|
||||
|
||||
object LegadoTheme {
|
||||
|
||||
val colorScheme: LegadoColorScheme
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoColorScheme.current
|
||||
|
||||
val isDark: Boolean
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoThemeColors.current.isDark
|
||||
|
||||
val seedColor: Color
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoThemeColors.current.seedColor
|
||||
|
||||
val paletteStyle: PaletteStyle
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoThemeColors.current.paletteStyle
|
||||
|
||||
val themeMode: ColorSchemeMode
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoThemeColors.current.themeMode
|
||||
|
||||
val composeEngine: String
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoThemeColors.current.composeEngine
|
||||
|
||||
val useDynamicColor: Boolean
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoThemeColors.current.useDynamicColor
|
||||
|
||||
val typography: LegadoTypography
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalLegadoTypography.current
|
||||
|
||||
}
|
||||
@@ -66,7 +66,8 @@ object ThemeManager {
|
||||
}
|
||||
|
||||
AppThemeMode.CUSTOM -> {
|
||||
CustomColorScheme(context.primaryColor, style)
|
||||
val colorSpec = ThemeResolver.resolveColorSpecFromMaterialVersion(materialVersion)
|
||||
CustomColorScheme(context.primaryColor, style, colorSpec)
|
||||
.getColorScheme(darkTheme)
|
||||
}
|
||||
|
||||
@@ -93,4 +94,4 @@ object ThemeManager {
|
||||
|
||||
return scheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.materialkolor.Contrast
|
||||
import com.materialkolor.PaletteStyle
|
||||
import com.materialkolor.dynamiccolor.ColorSpec
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
|
||||
|
||||
object ThemeResolver {
|
||||
|
||||
@@ -48,6 +49,17 @@ object ThemeResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fun resolveColorSchemeMode(value: String): ColorSchemeMode = when (value) {
|
||||
"0" -> ColorSchemeMode.System
|
||||
"1" -> ColorSchemeMode.Light
|
||||
"2" -> ColorSchemeMode.Dark
|
||||
else -> ColorSchemeMode.System
|
||||
}
|
||||
|
||||
fun isMiuixEngine(composeEngine: String): Boolean = composeEngine == "miuix"
|
||||
|
||||
fun isM3Engine(composeEngine: String): Boolean = composeEngine == "m3"
|
||||
|
||||
fun resolveColorSpecVersion(colorSpec: ThemeColorSpec): ColorSpec.SpecVersion {
|
||||
return when (colorSpec) {
|
||||
ThemeColorSpec.SPEC_2025 -> ColorSpec.SpecVersion.SPEC_2025
|
||||
@@ -55,4 +67,12 @@ object ThemeResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fun resolveColorSpecFromMaterialVersion(value: String?): ThemeColorSpec {
|
||||
return when (value) {
|
||||
"material3Expressive" -> ThemeColorSpec.SPEC_2025
|
||||
"material3" -> ThemeColorSpec.SPEC_2021
|
||||
else -> ThemeColorSpec.SPEC_2021
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.legado.app.ui.theme
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import top.yukonga.miuix.kmp.theme.TextStyles
|
||||
|
||||
|
||||
/**
|
||||
* 将 Miuix 的 TextStyles 语义化映射为 Material 3 的 Typography
|
||||
*/
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
fun miuixStylesToM3Typography(miuixStyles: TextStyles): Typography {
|
||||
return Typography(
|
||||
displayLarge = miuixStyles.title1, // 32.sp
|
||||
displayMedium = miuixStyles.title2, // 24.sp
|
||||
displaySmall = miuixStyles.title3, // 20.sp
|
||||
|
||||
headlineLarge = miuixStyles.title2, // 24.sp
|
||||
headlineMedium = miuixStyles.title3, // 20.sp
|
||||
headlineSmall = miuixStyles.title4, // 18.sp
|
||||
|
||||
titleLarge = miuixStyles.headline1, // 17.sp
|
||||
titleMedium = miuixStyles.headline2, // 16.sp
|
||||
titleSmall = miuixStyles.subtitle, // 14.sp, Bold
|
||||
|
||||
bodyLarge = miuixStyles.paragraph, // 17.sp, lineHeight 1.2em (如果不需要行高也可以换成 main)
|
||||
bodyMedium = miuixStyles.body1, // 16.sp
|
||||
bodySmall = miuixStyles.body2, // 14.sp
|
||||
|
||||
labelLarge = miuixStyles.button, // 17.sp
|
||||
labelMedium = miuixStyles.footnote1, // 13.sp
|
||||
labelSmall = miuixStyles.footnote2, // 11.sp
|
||||
|
||||
bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
|
||||
bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
|
||||
bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium),
|
||||
|
||||
labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium),
|
||||
labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
|
||||
labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.legado.app.ui.theme.hazeStyle
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.chrisbanes.haze.HazeStyle
|
||||
import dev.chrisbanes.haze.HazeTint
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
|
||||
object HazeLegado {
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun ultraThin(
|
||||
containerColor: Color = if (ThemeResolver.isMiuixEngine(composeEngine)) MiuixTheme.colorScheme.surface else MaterialTheme.colorScheme.surface,
|
||||
): HazeStyle = hazeLegado(
|
||||
containerColor = containerColor,
|
||||
lightAlpha = 0.35f,
|
||||
darkAlpha = 0.55f,
|
||||
)
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun regular(
|
||||
containerColor: Color = if (ThemeResolver.isMiuixEngine(composeEngine)) MiuixTheme.colorScheme.surface else MaterialTheme.colorScheme.surface,
|
||||
): HazeStyle = hazeLegado(
|
||||
containerColor = containerColor,
|
||||
lightAlpha = 0.73f,
|
||||
darkAlpha = 0.8f,
|
||||
)
|
||||
|
||||
private fun hazeLegado(
|
||||
containerColor: Color,
|
||||
lightAlpha: Float,
|
||||
darkAlpha: Float,
|
||||
): HazeStyle = HazeStyle(
|
||||
blurRadius = 24.dp,
|
||||
backgroundColor = containerColor,
|
||||
tint = HazeTint(
|
||||
containerColor.copy(alpha = if (containerColor.luminance() >= 0.5) lightAlpha else darkAlpha),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -8,12 +8,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -22,25 +19,27 @@ import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun CollapsibleHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
showIcon: Boolean = true,
|
||||
isCollapsed: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
title: String,
|
||||
subtitle: String? = null,
|
||||
titleContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||
) {
|
||||
Card(
|
||||
GlassCard(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, top = 4.dp, end = 16.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
),
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainer,
|
||||
onClick = onToggle
|
||||
) {
|
||||
Row(
|
||||
@@ -54,20 +53,20 @@ fun CollapsibleHeader(
|
||||
if (titleContent != null) {
|
||||
titleContent()
|
||||
} else {
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodySmallEmphasized.copy(
|
||||
style = LegadoTheme.typography.bodySmallEmphasized.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
color = LegadoTheme.colorScheme.primary
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
subtitle?.let {
|
||||
Text(
|
||||
AppText(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.labelSmallEmphasized.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
style = LegadoTheme.typography.labelSmallEmphasized.copy(
|
||||
color = LegadoTheme.colorScheme.onSurfaceVariant
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -81,12 +80,14 @@ fun CollapsibleHeader(
|
||||
label = "arrowRotation"
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
contentDescription = if (isCollapsed) "展开" else "折叠",
|
||||
modifier = Modifier.rotate(rotation),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
if (showIcon) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
contentDescription = if (isCollapsed) "展开" else "折叠",
|
||||
modifier = Modifier.rotate(rotation),
|
||||
tint = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import top.yukonga.miuix.kmp.basic.FloatingActionButton as MiuixFloatingActionButton
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppFloatingActionButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
tooltipText: String? = null,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
if (isMiuix) {
|
||||
MiuixFloatingActionButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
if (tooltipText != null) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
|
||||
TooltipAnchorPosition.Above
|
||||
),
|
||||
tooltip = { PlainTooltip { AppText(tooltipText) } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
} else {
|
||||
FloatingActionButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.BottomAppBarDefaults
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.basic.NavigationBarDisplayMode
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.NavigationBar as MiuixNavigationBar
|
||||
import top.yukonga.miuix.kmp.basic.NavigationBarItem as MiuixNavigationBarItem
|
||||
|
||||
@Composable
|
||||
fun AppNavigationBar(
|
||||
modifier: Modifier = Modifier,
|
||||
miuixMode: NavigationBarDisplayMode = NavigationBarDisplayMode.IconAndText,
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
val opacity = (ThemeConfig.bottomBarOpacity.coerceIn(0, 100)) / 100f
|
||||
|
||||
if (isMiuix) {
|
||||
val baseColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MiuixTheme.colorScheme.surface,
|
||||
blurAlpha = GlassDefaults.DefaultBlurAlpha
|
||||
)
|
||||
val finalColor = baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
|
||||
|
||||
MiuixNavigationBar(
|
||||
modifier = modifier,
|
||||
color = finalColor,
|
||||
mode = miuixMode,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
val baseColor = GlassDefaults.glassColor(
|
||||
noBlurColor = BottomAppBarDefaults.containerColor,
|
||||
blurAlpha = GlassDefaults.DefaultBlurAlpha
|
||||
)
|
||||
val finalColor = baseColor.copy(alpha = (baseColor.alpha * opacity).coerceIn(0f, 1f))
|
||||
|
||||
NavigationBar(
|
||||
modifier = modifier,
|
||||
containerColor = finalColor,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.AppNavigationBarItem(
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
labelString: String,
|
||||
miuixIcon: ImageVector,
|
||||
m3Icon: @Composable () -> Unit,
|
||||
m3IndicatorColor: Color,
|
||||
m3ShowLabel: Boolean,
|
||||
m3AlwaysShowLabel: Boolean,
|
||||
) {
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
if (isMiuix) {
|
||||
MiuixNavigationBarItem(
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
icon = miuixIcon,
|
||||
label = labelString,
|
||||
modifier = modifier
|
||||
)
|
||||
} else {
|
||||
NavigationBarItem(
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
icon = m3Icon,
|
||||
colors = NavigationBarItemDefaults.colors(indicatorColor = m3IndicatorColor),
|
||||
label = if (m3ShowLabel) {
|
||||
{ Text(labelString) }
|
||||
} else null,
|
||||
alwaysShowLabel = m3AlwaysShowLabel
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,89 +3,113 @@ package io.legado.app.ui.widget.components
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.theme.responsiveHazeEffect
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.LocalHazeState
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.theme.responsiveHazeSource
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.FabPosition as MiuixFabPosition
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold as MiuixScaffold
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppScaffold(
|
||||
modifier: Modifier = Modifier,
|
||||
title: (@Composable () -> Unit)? = null,
|
||||
subtitle: (@Composable () -> Unit)? = null,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
navigationIcon: @Composable () -> Unit = {},
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors(),
|
||||
topBar: @Composable () -> Unit = {},
|
||||
topBar: @Composable (HazeState) -> Unit = {},
|
||||
bottomBar: @Composable () -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
floatingActionButton: @Composable () -> Unit = {},
|
||||
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
||||
contentColor: Color = contentColorFor(MaterialTheme.colorScheme.background),
|
||||
contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface),
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
val hasImageBg = ThemeConfig.hasImageBg(isDark)
|
||||
val hazeState = remember { HazeState() }
|
||||
val composeEngine = LegadoTheme.composeEngine
|
||||
|
||||
val containerColor = if (hasImageBg) {
|
||||
Color.Transparent
|
||||
} else {
|
||||
MaterialTheme.colorScheme.background
|
||||
LegadoTheme.colorScheme.background
|
||||
}
|
||||
|
||||
val finalTopBar = @Composable {
|
||||
Box(modifier = Modifier.responsiveHazeEffect(state = hazeState)) {
|
||||
if (title != null) {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
colors = colors
|
||||
)
|
||||
} else {
|
||||
topBar()
|
||||
val miuixContainerColor = if (hasImageBg) {
|
||||
Color.Transparent
|
||||
} else {
|
||||
MiuixTheme.colorScheme.surface
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalHazeState provides if (ThemeConfig.enableBlur) hazeState else null
|
||||
) {
|
||||
when {
|
||||
ThemeResolver.isMiuixEngine(composeEngine) -> {
|
||||
val miuixFabPosition = when (floatingActionButtonPosition) {
|
||||
FabPosition.End -> MiuixFabPosition.End
|
||||
FabPosition.Center -> MiuixFabPosition.Center
|
||||
else -> MiuixFabPosition.End
|
||||
}
|
||||
MiuixScaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
topBar(hazeState)
|
||||
},
|
||||
bottomBar = bottomBar,
|
||||
snackbarHost = snackbarHost,
|
||||
floatingActionButton = floatingActionButton,
|
||||
floatingActionButtonPosition = miuixFabPosition,
|
||||
containerColor = miuixContainerColor,
|
||||
contentWindowInsets = contentWindowInsets
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.responsiveHazeSource(hazeState)
|
||||
) {
|
||||
content(paddingValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
topBar(hazeState)
|
||||
},
|
||||
bottomBar = bottomBar,
|
||||
snackbarHost = snackbarHost,
|
||||
floatingActionButton = floatingActionButton,
|
||||
floatingActionButtonPosition = floatingActionButtonPosition,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
contentWindowInsets = contentWindowInsets
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.responsiveHazeSource(hazeState)
|
||||
) {
|
||||
content(paddingValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = finalTopBar,
|
||||
bottomBar = bottomBar,
|
||||
snackbarHost = snackbarHost,
|
||||
floatingActionButton = floatingActionButton,
|
||||
floatingActionButtonPosition = floatingActionButtonPosition,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
contentWindowInsets = contentWindowInsets
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.responsiveHazeSource(hazeState)
|
||||
) {
|
||||
content(paddingValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.InputTransformation
|
||||
import androidx.compose.foundation.text.input.KeyboardActionHandler
|
||||
import androidx.compose.foundation.text.input.OutputTransformation
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TextFieldLabelPosition
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.Density
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.TextField as MiuixTextField
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppTextField(
|
||||
state: TextFieldState,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
// 1. 核心思想:保留 AppText 的统一样式与颜色降级参数
|
||||
textStyle: TextStyle? = null,
|
||||
textColor: Color = Color.Unspecified,
|
||||
// 2. 将 Label 统一定义为 String 以兼容两套引擎
|
||||
label: String? = null,
|
||||
// 3. 保留 M3 TextField 的所有其他参数
|
||||
labelPosition: TextFieldLabelPosition = TextFieldLabelPosition.Attached(),
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
prefix: @Composable (() -> Unit)? = null,
|
||||
suffix: @Composable (() -> Unit)? = null,
|
||||
supportingText: @Composable (() -> Unit)? = null,
|
||||
isError: Boolean = false,
|
||||
inputTransformation: InputTransformation? = null,
|
||||
outputTransformation: OutputTransformation? = null,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
onKeyboardAction: KeyboardActionHandler? = null,
|
||||
lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
|
||||
onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
shape: Shape = TextFieldDefaults.shape,
|
||||
colors: TextFieldColors = TextFieldDefaults.colors(),
|
||||
contentPadding: PaddingValues? = null,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
) {
|
||||
val baseStyle = textStyle ?: LegadoTheme.typography.bodyMedium
|
||||
val defaultTextColor = LegadoTheme.colorScheme.onSurface
|
||||
val finalTextColor = textColor.takeOrElse {
|
||||
baseStyle.color.takeOrElse { defaultTextColor }
|
||||
}
|
||||
val mergedTextStyle = baseStyle.merge(color = finalTextColor)
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
if (isMiuix) {
|
||||
MiuixTextField(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = MiuixTheme.textStyles.main,
|
||||
label = label ?: "",
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = trailingIcon,
|
||||
inputTransformation = inputTransformation,
|
||||
outputTransformation = outputTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
onKeyboardAction = onKeyboardAction,
|
||||
lineLimits = lineLimits,
|
||||
onTextLayout = onTextLayout,
|
||||
scrollState = scrollState,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
} else {
|
||||
val resolvedContentPadding =
|
||||
contentPadding ?: if (label == null || labelPosition is TextFieldLabelPosition.Above) {
|
||||
TextFieldDefaults.contentPaddingWithoutLabel()
|
||||
} else {
|
||||
TextFieldDefaults.contentPaddingWithLabel()
|
||||
}
|
||||
TextField(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = mergedTextStyle,
|
||||
labelPosition = labelPosition,
|
||||
label = label?.let { { AppText(it) } },
|
||||
placeholder = placeholder,
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = trailingIcon,
|
||||
prefix = prefix,
|
||||
suffix = suffix,
|
||||
supportingText = supportingText,
|
||||
isError = isError,
|
||||
inputTransformation = inputTransformation,
|
||||
outputTransformation = outputTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
onKeyboardAction = onKeyboardAction,
|
||||
lineLimits = lineLimits,
|
||||
onTextLayout = onTextLayout,
|
||||
scrollState = scrollState,
|
||||
shape = shape,
|
||||
colors = colors,
|
||||
contentPadding = resolvedContentPadding,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.ContainedLoadingIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -24,6 +23,8 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.text.AnimatedTextLine
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -70,7 +71,7 @@ fun EmptyMessageView(
|
||||
|
||||
AnimatedTextLine(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
style = LegadoTheme.typography.labelLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
softWrap = true,
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumFlexibleTopAppBar
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun GlassMediumFlexibleTopAppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: @Composable () -> Unit,
|
||||
subtitle: (@Composable () -> Unit)? = null,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
navigationIcon: @Composable () -> Unit = {},
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
colors: TopAppBarColors = GlassTopAppBarDefaults.glassColors()
|
||||
) {
|
||||
if (ThemeConfig.useFlexibleTopAppBar) {
|
||||
MediumFlexibleTopAppBar(
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = colors
|
||||
)
|
||||
} else {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = colors
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object GlassTopAppBarDefaults {
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun defaultScrollBehavior(): TopAppBarScrollBehavior {
|
||||
return if (ThemeConfig.useFlexibleTopAppBar) {
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
} else {
|
||||
TopAppBarDefaults.pinnedScrollBehavior()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun glassColors(): TopAppBarColors {
|
||||
|
||||
val containerColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.surface,
|
||||
blurAlpha = GlassDefaults.TransparentAlpha
|
||||
)
|
||||
|
||||
val scrolledContainerColor = if (ThemeConfig.enableBlur) {
|
||||
MaterialTheme.colorScheme.surface.copy(alpha = GlassDefaults.TransparentAlpha)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
|
||||
return TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = applyTopBarOpacity(containerColor),
|
||||
scrolledContainerColor = applyTopBarOpacity(scrolledContainerColor)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun containerColor(): Color {
|
||||
val baseColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.surface,
|
||||
blurAlpha = GlassDefaults.TransparentAlpha
|
||||
)
|
||||
return applyTopBarOpacity(baseColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun scrolledContainerColor(): Color {
|
||||
val baseColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
blurAlpha = GlassDefaults.TransparentAlpha
|
||||
)
|
||||
return applyTopBarOpacity(baseColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun controlContainerColor(): Color {
|
||||
val baseColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
blurAlpha = GlassDefaults.DefaultBlurAlpha
|
||||
)
|
||||
return applyTopBarOpacity(baseColor)
|
||||
}
|
||||
|
||||
private fun applyTopBarOpacity(color: Color): Color {
|
||||
val opacity = (ThemeConfig.topBarOpacity.coerceIn(0, 100)) / 100f
|
||||
return color.copy(alpha = (color.alpha * opacity).coerceIn(0f, 1f))
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
@@ -18,7 +16,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -26,45 +23,36 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.button.SmallTextButton
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.SettingItem
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GroupManageBottomSheet(
|
||||
show: Boolean,
|
||||
groups: List<String>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onUpdateGroup: (oldGroup: String, newGroup: String) -> Unit,
|
||||
onDeleteGroup: (group: String) -> Unit
|
||||
) {
|
||||
GlassModalBottomSheet(
|
||||
onDismissRequest = onDismissRequest
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = stringResource(R.string.group_manage),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
text = stringResource(R.string.group_manage),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(groups, key = { it }) { group ->
|
||||
GroupItem(
|
||||
group = group,
|
||||
onUpdateGroup = onUpdateGroup,
|
||||
onDeleteGroup = onDeleteGroup
|
||||
)
|
||||
}
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(groups, key = { it }) { group ->
|
||||
GroupItem(
|
||||
group = group,
|
||||
onUpdateGroup = onUpdateGroup,
|
||||
onDeleteGroup = onDeleteGroup
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +103,7 @@ private fun GroupItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 48.dp),
|
||||
label = { Text(stringResource(R.string.edit)) },
|
||||
label = { AppText(stringResource(R.string.edit)) },
|
||||
contentPadding = PaddingValues(
|
||||
top = 4.dp,
|
||||
bottom = 4.dp,
|
||||
|
||||
@@ -10,6 +10,40 @@ import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.basic.Switch as MiuixSwitch
|
||||
|
||||
@Composable
|
||||
fun AdaptiveSwitch(
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
checkedIcon: ImageVector = Icons.Filled.Check,
|
||||
uncheckedIcon: ImageVector? = null,
|
||||
showIcon: Boolean = true
|
||||
) {
|
||||
val composeEngine = LegadoTheme.composeEngine
|
||||
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
MiuixSwitch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = modifier,
|
||||
enabled = enabled
|
||||
)
|
||||
} else {
|
||||
IconSwitch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
enabled = enabled,
|
||||
checkedIcon = checkedIcon,
|
||||
uncheckedIcon = uncheckedIcon,
|
||||
showIcon = showIcon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IconSwitch(
|
||||
|
||||
@@ -1,33 +1,14 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.VerticalAlignTop
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -38,13 +19,15 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
||||
import io.legado.app.ui.widget.components.icon.AppIcons
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -53,10 +36,10 @@ fun SearchBarSection(
|
||||
onQueryChange: (String) -> Unit,
|
||||
placeholder: String = "搜索...",
|
||||
leadingIcon: @Composable (() -> Unit)? = {
|
||||
SmallIconButton(
|
||||
icon = Icons.Default.Search,
|
||||
contentDescription = null,
|
||||
onClick = {})
|
||||
AppIcon(
|
||||
imageVector = AppIcons.Search,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
scrollState: LazyListState? = null,
|
||||
@@ -65,7 +48,6 @@ fun SearchBarSection(
|
||||
dropdownMenu: (@Composable (onDismiss: () -> Unit) -> Unit)? = null
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
val textFieldState = rememberTextFieldState(initialText = query)
|
||||
|
||||
LaunchedEffect(query) {
|
||||
@@ -91,79 +73,19 @@ fun SearchBarSection(
|
||||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
|
||||
shape = RoundedCornerShape(32.dp),
|
||||
color = backgroundColor
|
||||
) {
|
||||
TextField(
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
val searchTextField = @Composable {
|
||||
AppTextField(
|
||||
state = textFieldState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 48.dp)
|
||||
.padding(horizontal = 4.dp),
|
||||
placeholder = { Text(placeholder) },
|
||||
.then(
|
||||
if (isMiuix) Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
else Modifier
|
||||
),
|
||||
placeholder = { AppText(placeholder) },
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(end = 4.dp)
|
||||
.animateContentSize()
|
||||
) {
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = textFieldState.text.isNotEmpty(),
|
||||
enter = fadeIn() + scaleIn(),
|
||||
exit = fadeOut() + scaleOut()
|
||||
) {
|
||||
SmallIconButton(
|
||||
onClick = { textFieldState.clearText() },
|
||||
icon = Icons.Default.Clear,
|
||||
contentDescription = "清空输入"
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = showScrollToTop,
|
||||
enter = fadeIn() + scaleIn(),
|
||||
exit = fadeOut() + scaleOut()
|
||||
) {
|
||||
SmallIconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scrollState?.animateScrollToItem(0)
|
||||
}
|
||||
},
|
||||
icon = Icons.Default.VerticalAlignTop,
|
||||
contentDescription = "回到顶部"
|
||||
)
|
||||
}
|
||||
|
||||
if (trailingIcon != null) {
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (dropdownMenu != null) showMenu = true
|
||||
}
|
||||
) {
|
||||
trailingIcon()
|
||||
}
|
||||
|
||||
if (dropdownMenu != null) {
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
dropdownMenu { showMenu = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
@@ -171,13 +93,21 @@ fun SearchBarSection(
|
||||
disabledContainerColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
contentPadding = PaddingValues(
|
||||
top = 4.dp,
|
||||
bottom = 4.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (isMiuix) {
|
||||
searchTextField()
|
||||
} else {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
|
||||
shape = RoundedCornerShape(32.dp),
|
||||
color = backgroundColor
|
||||
) {
|
||||
searchTextField()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.material3.HorizontalFloatingToolbar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
@@ -27,6 +26,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
data class SelectionActions(
|
||||
val primaryAction: ActionItem,
|
||||
@@ -84,7 +84,7 @@ fun SelectionBottomBar(
|
||||
) {
|
||||
secondaryActions.forEach { action ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(action.text) },
|
||||
text = { AppText(action.text) },
|
||||
leadingIcon = action.icon,
|
||||
onClick = {
|
||||
action.onClick()
|
||||
@@ -101,7 +101,7 @@ fun SelectionBottomBar(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
|
||||
TooltipAnchorPosition.Above
|
||||
),
|
||||
tooltip = { PlainTooltip { Text(primaryAction.text) } },
|
||||
tooltip = { PlainTooltip { AppText(primaryAction.text) } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
FilledIconButton(
|
||||
|
||||
@@ -7,14 +7,14 @@ import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.title.AdaptiveTitle
|
||||
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
|
||||
|
||||
/**
|
||||
* Settings Group Container by https://github.com/wxxsfxyzm/InstallerX-Revived
|
||||
@@ -29,24 +29,33 @@ fun SplicedColumnGroup(
|
||||
title: String = "",
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier.padding(top = 8.dp, bottom = 16.dp)) {
|
||||
val composeEngine = LegadoTheme.composeEngine
|
||||
Column(modifier = modifier.padding(top = 8.dp, bottom = 8.dp)) {
|
||||
if (title.isNotEmpty()) {
|
||||
Text(
|
||||
AdaptiveTitle(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
) {
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
MiuixCard {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.clip(RoundedCornerShape(16.dp)),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
content()
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.legado.app.ui.widget.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.button.SmallOutlinedIconButton
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
|
||||
@Composable
|
||||
fun ValueStepper(
|
||||
value: Float,
|
||||
displayValue: Float,
|
||||
valueRange: ClosedFloatingPointRange<Float>,
|
||||
onValueChange: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
SmallOutlinedIconButton(
|
||||
onClick = {
|
||||
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
|
||||
onValueChange(newValue)
|
||||
},
|
||||
icon = Icons.Default.Remove
|
||||
)
|
||||
TextCard(
|
||||
cornerRadius = 8.dp,
|
||||
horizontalPadding = 8.dp,
|
||||
verticalPadding = 4.dp,
|
||||
text = displayValue.toInt().toString(),
|
||||
backgroundColor = LegadoTheme.colorScheme.surfaceContainer,
|
||||
contentColor = LegadoTheme.colorScheme.onSurface
|
||||
)
|
||||
SmallOutlinedIconButton(
|
||||
onClick = {
|
||||
val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
|
||||
onValueChange(newValue)
|
||||
},
|
||||
icon = Icons.Default.Add
|
||||
)
|
||||
}
|
||||
}
|
||||
+23
-31
@@ -7,15 +7,11 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -26,11 +22,15 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.button.PrimaryButton
|
||||
import io.legado.app.ui.widget.components.button.SecondaryButton
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BookmarkEditSheet(
|
||||
show: Boolean,
|
||||
bookmark: Bookmark,
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (Bookmark) -> Unit,
|
||||
@@ -41,25 +41,20 @@ fun BookmarkEditSheet(
|
||||
var bookText by remember { mutableStateOf(bookmark.bookText) }
|
||||
var content by remember { mutableStateOf(bookmark.content) }
|
||||
|
||||
GlassModalBottomSheet(
|
||||
onDismissRequest = onDismiss
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismiss,
|
||||
title = bookmark.chapterName,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Text(
|
||||
text = bookmark.chapterName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = bookText,
|
||||
onValueChange = { bookText = it },
|
||||
label = { Text("原文") },
|
||||
label = { AppText("原文") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 10
|
||||
)
|
||||
@@ -69,7 +64,7 @@ fun BookmarkEditSheet(
|
||||
OutlinedTextField(
|
||||
value = content,
|
||||
onValueChange = { content = it },
|
||||
label = { Text("摘要/笔记") },
|
||||
label = { AppText("摘要/笔记") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 5
|
||||
)
|
||||
@@ -81,15 +76,13 @@ fun BookmarkEditSheet(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedButton(
|
||||
SecondaryButton(
|
||||
onClick = { showDeleteConfirmDialog = true },
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("删除")
|
||||
}
|
||||
modifier = Modifier.weight(1f),
|
||||
text = "删除"
|
||||
)
|
||||
|
||||
Button(
|
||||
PrimaryButton(
|
||||
onClick = {
|
||||
val newBookmark = bookmark.apply {
|
||||
this.bookText = bookText
|
||||
@@ -97,10 +90,9 @@ fun BookmarkEditSheet(
|
||||
}
|
||||
onSave(newBookmark)
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("保存")
|
||||
}
|
||||
modifier = Modifier.weight(1f),
|
||||
text = "保存"
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
@@ -109,8 +101,8 @@ fun BookmarkEditSheet(
|
||||
if (showDeleteConfirmDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteConfirmDialog = false },
|
||||
title = { Text("确认删除") },
|
||||
text = { Text("你确定要删除这条书签吗?") },
|
||||
title = { AppText("确认删除") },
|
||||
text = { AppText("你确定要删除这条书签吗?") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
@@ -119,14 +111,14 @@ fun BookmarkEditSheet(
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
|
||||
) {
|
||||
Text("删除")
|
||||
AppText("删除")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { showDeleteConfirmDialog = false }
|
||||
) {
|
||||
Text("取消")
|
||||
AppText("取消")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -9,9 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -19,6 +17,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -30,7 +30,7 @@ fun BookmarkItem(
|
||||
onLongClick: () -> Unit
|
||||
) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (isDur) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
|
||||
targetValue = if (isDur) LegadoTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
|
||||
else Color.Transparent,
|
||||
label = "BgColor"
|
||||
)
|
||||
@@ -49,29 +49,29 @@ fun BookmarkItem(
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = bookmark.chapterName,
|
||||
style = MaterialTheme.typography.titleSmallEmphasized,
|
||||
color = if (isDur) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary
|
||||
style = LegadoTheme.typography.titleSmallEmphasized,
|
||||
color = if (isDur) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.secondary
|
||||
)
|
||||
|
||||
if (bookmark.bookText.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = bookmark.bookText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
color = LegadoTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
if (bookmark.content.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = bookmark.content,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
color = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package io.legado.app.ui.widget.components.button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@Composable
|
||||
fun AlertButton(
|
||||
@@ -24,6 +24,6 @@ fun AlertButton(
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
) {
|
||||
Text(text = text)
|
||||
AppText(text = text)
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ToggleButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -21,6 +19,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -73,9 +73,9 @@ fun AnimatedActionButton(
|
||||
AnimatedVisibility(
|
||||
visible = showText
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = if (lastCheckedState) activeText else inactiveText,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
maxLines = 1,
|
||||
softWrap = false
|
||||
|
||||
@@ -8,11 +8,11 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.material3.ContainedLoadingIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -38,7 +38,7 @@ fun AnimatedTextButton(
|
||||
if (loading) {
|
||||
ContainedLoadingIndicator()
|
||||
} else {
|
||||
Text(text)
|
||||
AppText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.legado.app.ui.widget.components.button
|
||||
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.basic.Button as MiuixButton
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Text as MiuixText
|
||||
|
||||
@Composable
|
||||
fun PrimaryButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
text: String
|
||||
) {
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
MiuixButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = MiuixButtonDefaults.buttonColorsPrimary()
|
||||
) {
|
||||
MiuixText(
|
||||
text = text
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SecondaryButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
text: String
|
||||
) {
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
MiuixButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = MiuixButtonDefaults.buttonColors()
|
||||
) {
|
||||
MiuixText(
|
||||
text = text
|
||||
)
|
||||
}
|
||||
} else {
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
) {
|
||||
Text(
|
||||
text = text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.legado.app.ui.widget.components.button
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonColors
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
|
||||
import top.yukonga.miuix.kmp.basic.IconButtonDefaults as MiuixIconButtonDefaults
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun AppIconButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
//M3
|
||||
colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
|
||||
shape: Shape = IconButtonDefaults.standardShape,
|
||||
// MIUIX
|
||||
holdDownState: Boolean = false,
|
||||
miuixBackgroundColor: Color = Color.Unspecified,
|
||||
miuixCornerRadius: Dp? = null,
|
||||
miuixMinHeight: Dp? = null,
|
||||
miuixMinWidth: Dp? = null,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
if (isMiuix) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
holdDownState = holdDownState,
|
||||
backgroundColor = miuixBackgroundColor,
|
||||
cornerRadius = MiuixIconButtonDefaults.CornerRadius,
|
||||
minHeight = MiuixIconButtonDefaults.MinHeight,
|
||||
minWidth = MiuixIconButtonDefaults.MinWidth,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = colors,
|
||||
interactionSource = interactionSource,
|
||||
shape = shape,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.legado.app.ui.widget.components.button
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.legado.app.R
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.extended.Back
|
||||
import top.yukonga.miuix.kmp.theme.LocalContentColor
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
|
||||
|
||||
@Composable
|
||||
fun MiuixIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
imageVector: ImageVector = MiuixIcons.Regular.Back,
|
||||
tint: Color = LocalContentColor.current,
|
||||
contentDescription: String? = stringResource(id = R.string.back),
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
) {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
tint = tint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MiuixToggleButton(
|
||||
modifier: Modifier = Modifier,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
iconChecked: ImageVector,
|
||||
iconUnchecked: ImageVector = iconChecked,
|
||||
checkedTint: Color = MiuixTheme.colorScheme.primary,
|
||||
uncheckedTint: Color = LocalContentColor.current,
|
||||
contentDescription: String? = null,
|
||||
) {
|
||||
MiuixIconButton(
|
||||
onClick = { onCheckedChange(!checked) },
|
||||
modifier = modifier
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (checked) iconChecked else iconUnchecked,
|
||||
contentDescription = contentDescription,
|
||||
tint = if (checked) checkedTint else uncheckedTint
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.legado.app.ui.widget.components.button
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -15,11 +17,9 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.IconToggleButtonShapes
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedIconButton
|
||||
import androidx.compose.material3.OutlinedIconToggleButton
|
||||
import androidx.compose.material3.OutlinedToggleButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -31,7 +31,13 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.delay
|
||||
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -41,22 +47,33 @@ fun SmallIconButton(
|
||||
icon: ImageVector,
|
||||
contentDescription: String? = null
|
||||
) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.size(
|
||||
IconButtonDefaults.extraSmallContainerSize(
|
||||
IconButtonDefaults.IconButtonWidthOption.Uniform
|
||||
)
|
||||
),
|
||||
shape = IconButtonDefaults.extraSmallRoundShape,
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick
|
||||
) {
|
||||
Icon(
|
||||
MiuixIcon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
}
|
||||
} else {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.size(
|
||||
IconButtonDefaults.extraSmallContainerSize(
|
||||
IconButtonDefaults.IconButtonWidthOption.Uniform
|
||||
)
|
||||
),
|
||||
shape = IconButtonDefaults.extraSmallRoundShape,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +82,24 @@ fun SmallIconButton(
|
||||
fun SmallOutlinedIconButton(
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null
|
||||
) {
|
||||
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
|
||||
if (isMiuix) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier.size(32.dp),
|
||||
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
|
||||
) {
|
||||
MiuixIcon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
OutlinedIconButton(
|
||||
onClick = onClick,
|
||||
@@ -87,6 +120,7 @@ fun SmallOutlinedIconButton(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -94,27 +128,42 @@ fun SmallOutlinedIconButton(
|
||||
fun SmallTonalIconButton(
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null
|
||||
) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
FilledTonalIconButton(
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
|
||||
|
||||
if (isMiuix) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.size(
|
||||
IconButtonDefaults.extraSmallContainerSize(
|
||||
IconButtonDefaults.IconButtonWidthOption.Uniform
|
||||
)
|
||||
),
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
colors = IconButtonDefaults.filledTonalIconButtonColors()
|
||||
modifier = modifier.size(32.dp),
|
||||
backgroundColor = LegadoTheme.colorScheme.surfaceContainer
|
||||
) {
|
||||
Icon(
|
||||
MiuixIcon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(
|
||||
IconButtonDefaults.extraSmallIconSize
|
||||
)
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
FilledTonalIconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier.size(
|
||||
IconButtonDefaults.extraSmallContainerSize(
|
||||
IconButtonDefaults.IconButtonWidthOption.Uniform
|
||||
)
|
||||
),
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
colors = IconButtonDefaults.filledTonalIconButtonColors()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,35 +176,67 @@ fun SmallOutlinedIconToggleButton(
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null
|
||||
) {
|
||||
val defaultShape = IconButtonDefaults.extraSmallRoundShape
|
||||
val pressedShape = IconButtonDefaults.extraSmallPressedShape
|
||||
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
|
||||
|
||||
val toggleShapes = remember(defaultShape, checkedShape) {
|
||||
IconToggleButtonShapes(
|
||||
shape = defaultShape,
|
||||
pressedShape = pressedShape,
|
||||
checkedShape = checkedShape
|
||||
if (isMiuix) {
|
||||
|
||||
val containerColor by animateColorAsState(
|
||||
targetValue = if (checked) LegadoTheme.colorScheme.primaryContainer else LegadoTheme.colorScheme.surfaceContainer,
|
||||
animationSpec = tween(150),
|
||||
label = "MiuixToggleContainerColor"
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
OutlinedIconToggleButton(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = modifier.size(
|
||||
IconButtonDefaults.extraSmallContainerSize(
|
||||
IconButtonDefaults.IconButtonWidthOption.Uniform
|
||||
)
|
||||
),
|
||||
shapes = toggleShapes
|
||||
val iconTint by animateColorAsState(
|
||||
targetValue = if (checked) LegadoTheme.colorScheme.onPrimaryContainer else LegadoTheme.colorScheme.onSurfaceVariant,
|
||||
animationSpec = tween(150),
|
||||
label = "MiuixToggleIconTint"
|
||||
)
|
||||
|
||||
MiuixIconButton(
|
||||
onClick = { onCheckedChange(!checked) },
|
||||
modifier = modifier
|
||||
.size(32.dp),
|
||||
backgroundColor = containerColor
|
||||
) {
|
||||
Icon(
|
||||
MiuixIcon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
|
||||
tint = iconTint,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
val defaultShape = IconButtonDefaults.extraSmallRoundShape
|
||||
val pressedShape = IconButtonDefaults.extraSmallPressedShape
|
||||
val checkedShape = IconButtonDefaults.extraSmallSelectedRoundShape
|
||||
|
||||
val toggleShapes = remember(defaultShape, checkedShape) {
|
||||
IconToggleButtonShapes(
|
||||
shape = defaultShape,
|
||||
pressedShape = pressedShape,
|
||||
checkedShape = checkedShape
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
OutlinedIconToggleButton(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = modifier.size(
|
||||
IconButtonDefaults.extraSmallContainerSize(
|
||||
IconButtonDefaults.IconButtonWidthOption.Uniform
|
||||
)
|
||||
),
|
||||
shapes = toggleShapes
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(IconButtonDefaults.extraSmallIconSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,9 +285,9 @@ fun SmallAnimatedActionButton(
|
||||
AnimatedVisibility(
|
||||
visible = showText
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = if (lastCheckedState) activeText else inactiveText,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(start = 6.dp),
|
||||
maxLines = 1,
|
||||
softWrap = false
|
||||
|
||||
@@ -6,12 +6,18 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import top.yukonga.miuix.kmp.basic.Button as MiuixButton
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Text as MiuixText
|
||||
|
||||
@Composable
|
||||
fun SmallTextButton(
|
||||
@@ -20,21 +26,33 @@ fun SmallTextButton(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
MiuixButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
colors = MiuixButtonDefaults.buttonColors()
|
||||
) {
|
||||
MiuixText(
|
||||
text = text
|
||||
)
|
||||
}
|
||||
} else {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
AppText(
|
||||
text = text,
|
||||
style = LegadoTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedIconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ToggleButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -35,7 +34,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.icon.AppIcons
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
enum class TopBarButtonVariant {
|
||||
@@ -105,17 +108,26 @@ fun TopBarButton(
|
||||
fun TopbarNavigationButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
imageVector: ImageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
imageVector: ImageVector = AppIcons.Back,
|
||||
contentDescription: String? = "返回",
|
||||
style: TopBarButtonVariant = TopBarButtonVariant.Filled
|
||||
) {
|
||||
TopBarButton(
|
||||
onClick = onClick,
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.padding(horizontal = 12.dp),
|
||||
style = style
|
||||
)
|
||||
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick,
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
} else {
|
||||
TopBarButton(
|
||||
onClick = onClick,
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.padding(horizontal = 12.dp),
|
||||
style = style
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -126,21 +138,29 @@ fun TopBarActionButton(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val enableProgressive = ThemeConfig.enableProgressiveBlur
|
||||
|
||||
if (enableProgressive) {
|
||||
TopBarButton(
|
||||
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
|
||||
MiuixIconButton(
|
||||
onClick = onClick,
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.padding(end = 12.dp),
|
||||
style = TopBarButtonVariant.Filled
|
||||
modifier = modifier.padding(end = 8.dp),
|
||||
)
|
||||
} else {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
if (enableProgressive) {
|
||||
TopBarButton(
|
||||
onClick = onClick,
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.padding(end = 12.dp),
|
||||
style = TopBarButtonVariant.Filled
|
||||
)
|
||||
} else {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,43 +185,51 @@ fun TopBarAnimatedActionButton(
|
||||
showText = false
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton(
|
||||
modifier = modifier.height(36.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp),
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
lastCheckedState = it
|
||||
onCheckedChange(it)
|
||||
showText = true
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
||||
AnimatedContent(
|
||||
targetState = checked,
|
||||
label = "IconAnimation"
|
||||
) { targetChecked ->
|
||||
AnimatedIcon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = if (targetChecked) iconChecked else iconUnchecked,
|
||||
contentDescription = null
|
||||
)
|
||||
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
|
||||
MiuixToggleButton(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
iconChecked = iconChecked,
|
||||
iconUnchecked = iconUnchecked
|
||||
)
|
||||
} else {
|
||||
ToggleButton(
|
||||
modifier = modifier.height(36.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp),
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
lastCheckedState = it
|
||||
onCheckedChange(it)
|
||||
showText = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = showText
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (lastCheckedState) activeText else inactiveText,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
maxLines = 1,
|
||||
softWrap = false
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = checked,
|
||||
label = "IconAnimation"
|
||||
) { targetChecked ->
|
||||
AnimatedIcon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = if (targetChecked) iconChecked else iconUnchecked,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = showText
|
||||
) {
|
||||
AppText(
|
||||
text = if (lastCheckedState) activeText else inactiveText,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
maxLines = 1,
|
||||
softWrap = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,79 @@ import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CardElevation
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.Card as MiuixCard
|
||||
import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun BaseCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
shape: Shape = CardDefaults.shape,
|
||||
cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
|
||||
containerColor: Color? = null,
|
||||
contentColor: Color? = null,
|
||||
elevation: CardElevation = CardDefaults.cardElevation(),
|
||||
border: BorderStroke? = null,
|
||||
alpha: Float = 1f,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
|
||||
val colors = MiuixCardDefaults.defaultColors(
|
||||
color = (containerColor
|
||||
?: MiuixTheme.colorScheme.secondaryContainer).copy(alpha = alpha),
|
||||
contentColor = contentColor ?: MiuixTheme.colorScheme.onSurface
|
||||
)
|
||||
MiuixCard(
|
||||
modifier = modifier,
|
||||
cornerRadius = cornerRadius,
|
||||
onClick = onClick,
|
||||
content = content,
|
||||
colors = colors
|
||||
)
|
||||
} else {
|
||||
val colors = CardDefaults.cardColors(
|
||||
containerColor = (containerColor ?: MaterialTheme.colorScheme.secondaryContainer).copy(
|
||||
alpha = alpha
|
||||
),
|
||||
contentColor = contentColor ?: MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f),
|
||||
disabledContentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f)
|
||||
)
|
||||
if (onClick != null) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
colors = colors,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
onClick = onClick,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
colors = colors,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@@ -19,43 +84,50 @@ fun GlassCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
shape: Shape = CardDefaults.shape,
|
||||
colors: CardColors? = null,
|
||||
cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
|
||||
containerColor: Color? = null,
|
||||
contentColor: Color? = null,
|
||||
elevation: CardElevation = CardDefaults.cardElevation(),
|
||||
border: BorderStroke? = null,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
val containerAlpha = ThemeConfig.containerOpacity / 100f
|
||||
BaseCard(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
shape = shape,
|
||||
cornerRadius = cornerRadius,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
alpha = ThemeConfig.containerOpacity / 100f,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
val baseColors = colors ?: CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun NormalCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
shape: Shape = CardDefaults.shape,
|
||||
cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
|
||||
containerColor: Color? = null,
|
||||
contentColor: Color? = null,
|
||||
elevation: CardElevation = CardDefaults.cardElevation(),
|
||||
border: BorderStroke? = null,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
BaseCard(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
shape = shape,
|
||||
cornerRadius = cornerRadius,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
alpha = 1f,
|
||||
content = content
|
||||
)
|
||||
|
||||
val finalColors = CardDefaults.cardColors(
|
||||
containerColor = baseColors.containerColor.copy(alpha = containerAlpha),
|
||||
contentColor = baseColors.contentColor,
|
||||
disabledContainerColor = baseColors.disabledContainerColor.copy(alpha = containerAlpha),
|
||||
disabledContentColor = baseColors.disabledContentColor
|
||||
)
|
||||
|
||||
if (onClick != null) {
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
colors = finalColors,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
colors = finalColors,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -41,10 +39,17 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.AdaptiveSwitch
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import sh.calvin.reorderable.ReorderableItem
|
||||
import sh.calvin.reorderable.ReorderableLazyListState
|
||||
import top.yukonga.miuix.kmp.basic.BasicComponent
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
|
||||
@Composable
|
||||
fun SelectionItemCard(
|
||||
@@ -66,11 +71,14 @@ fun SelectionItemCard(
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
val composeEngine = ThemeResolver.isMiuixEngine(composeEngine)
|
||||
val animatedContainerColor by animateColorAsState(
|
||||
targetValue = if (isSelected)
|
||||
selectedContainerColor ?: MaterialTheme.colorScheme.secondaryContainer
|
||||
selectedContainerColor
|
||||
?: if (composeEngine) MiuixTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer
|
||||
else
|
||||
containerColor ?: MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
containerColor
|
||||
?: if (composeEngine) MiuixTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
|
||||
label = "CardColor"
|
||||
)
|
||||
@@ -78,10 +86,9 @@ fun SelectionItemCard(
|
||||
GlassCard(
|
||||
onClick = onToggleSelection,
|
||||
modifier = modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = CardDefaults.cardColors(containerColor = animatedContainerColor),
|
||||
containerColor = animatedContainerColor,
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = elevation)
|
||||
) {
|
||||
Row(
|
||||
@@ -113,28 +120,49 @@ fun SelectionItemCard(
|
||||
}
|
||||
}
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier.weight(1f),
|
||||
headlineContent = {
|
||||
Text(
|
||||
if (composeEngine) {
|
||||
BasicComponent(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
AppText(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
style = LegadoTheme.typography.titleSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
supportingContent = if (!subtitle.isNullOrBlank()) {
|
||||
{
|
||||
Text(
|
||||
if (!subtitle.isNullOrBlank()) {
|
||||
AppText(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ListItem(
|
||||
modifier = Modifier.weight(1f),
|
||||
headlineContent = {
|
||||
AppText(
|
||||
text = title,
|
||||
style = LegadoTheme.typography.titleSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
supportingContent = if (!subtitle.isNullOrBlank()) {
|
||||
{
|
||||
AppText(
|
||||
text = subtitle,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -143,7 +171,7 @@ fun SelectionItemCard(
|
||||
.padding(end = 8.dp)
|
||||
) {
|
||||
onEnabledChange?.let {
|
||||
Switch(
|
||||
AdaptiveSwitch(
|
||||
modifier = Modifier.scale(0.8f),
|
||||
checked = isEnabled,
|
||||
onCheckedChange = it
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package io.legado.app.ui.widget.components.card
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CardElevation
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.basic.BasicComponent
|
||||
|
||||
// import top.yukonga.miuix.kmp.basic.theme.LocalColors as MiuixLocalColors
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun SettingCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
shape: Shape = CardDefaults.shape,
|
||||
colors: CardColors? = null,
|
||||
elevation: CardElevation = CardDefaults.cardElevation(),
|
||||
border: BorderStroke? = null,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
val composeEngine = LegadoTheme.composeEngine
|
||||
val containerAlpha = ThemeConfig.containerOpacity / 100f
|
||||
|
||||
if (ThemeResolver.isMiuixEngine(composeEngine)) {
|
||||
BasicComponent(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
val baseColors = colors ?: CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
|
||||
val finalColors = CardDefaults.cardColors(
|
||||
containerColor = baseColors.containerColor.copy(alpha = containerAlpha),
|
||||
contentColor = baseColors.contentColor,
|
||||
disabledContainerColor = baseColors.disabledContainerColor.copy(alpha = containerAlpha),
|
||||
disabledContentColor = baseColors.disabledContentColor
|
||||
)
|
||||
|
||||
if (onClick != null) {
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
colors = finalColors,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
colors = finalColors,
|
||||
elevation = elevation,
|
||||
border = border,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,50 +6,47 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.widget.components.AnimatedTextLine
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
||||
import io.legado.app.ui.widget.components.text.AnimatedTextLine
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun PreviewTextCard() {
|
||||
MaterialTheme {
|
||||
TextCard(
|
||||
text = "v1.0.0"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun TextCard(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
icon: ImageVector? = null,
|
||||
onClick: (() -> Unit)? = null,
|
||||
backgroundColor: Color = colorScheme.primaryContainer,
|
||||
contentColor: Color = colorScheme.onPrimaryContainer,
|
||||
backgroundColor: Color? = null,
|
||||
contentColor: Color? = null,
|
||||
cornerRadius: Dp = 8.dp,
|
||||
horizontalPadding: Dp = 8.dp,
|
||||
verticalPadding: Dp = 2.dp,
|
||||
iconSize: Dp = 14.dp,
|
||||
spacing: Dp = 4.dp,
|
||||
textStyle: TextStyle = MaterialTheme.typography.labelSmall
|
||||
textStyle: TextStyle = LegadoTheme.typography.labelSmallEmphasized
|
||||
) {
|
||||
GlassCard(
|
||||
val defaultBackground = LegadoTheme.colorScheme.surfaceContainer
|
||||
val defaultContent = LegadoTheme.colorScheme.onSurface
|
||||
|
||||
val finalBackgroundColor = backgroundColor ?: defaultBackground
|
||||
val finalContentColor = contentColor ?: defaultContent
|
||||
|
||||
NormalCard(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(cornerRadius),
|
||||
colors = CardDefaults.cardColors(containerColor = backgroundColor),
|
||||
containerColor = finalBackgroundColor,
|
||||
contentColor = finalContentColor,
|
||||
onClick = onClick
|
||||
) {
|
||||
Row(
|
||||
@@ -61,10 +58,10 @@ fun TextCard(
|
||||
) {
|
||||
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
AppIcon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = contentColor,
|
||||
tint = finalContentColor,
|
||||
modifier = Modifier.size(iconSize)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(spacing))
|
||||
@@ -73,7 +70,7 @@ fun TextCard(
|
||||
AnimatedTextLine(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = contentColor
|
||||
color = finalContentColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
|
||||
@Composable
|
||||
fun CheckboxItem(
|
||||
@@ -45,9 +46,9 @@ fun CheckboxItem(
|
||||
enabled = enabled,
|
||||
modifier = Modifier.alpha(alpha)
|
||||
)
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp)
|
||||
|
||||
@@ -7,12 +7,12 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
|
||||
@Composable
|
||||
@@ -44,8 +44,6 @@ fun BookshelfCover(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(2.dp),
|
||||
backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
cornerRadius = 4.dp,
|
||||
horizontalPadding = 4.dp,
|
||||
verticalPadding = 0.dp
|
||||
@@ -59,8 +57,8 @@ fun BookshelfCover(
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 4.dp, vertical = 6.dp)
|
||||
.height(3.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
|
||||
color = LegadoTheme.colorScheme.primary,
|
||||
trackColor = LegadoTheme.colorScheme.primary.copy(alpha = 0.2f),
|
||||
strokeCap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -41,11 +40,14 @@ import com.github.skydoves.colorpicker.compose.BrightnessSlider
|
||||
import com.github.skydoves.colorpicker.compose.HsvColorPicker
|
||||
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ColorPickerSheet(
|
||||
show: Boolean,
|
||||
initialColor: Int,
|
||||
onDismissRequest: () -> Unit,
|
||||
onColorSelected: (Int) -> Unit
|
||||
@@ -79,16 +81,16 @@ fun ColorPickerSheet(
|
||||
)
|
||||
}
|
||||
|
||||
GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
|
||||
AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
AppText(
|
||||
text = "颜色选择",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
style = LegadoTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
@@ -168,9 +170,9 @@ fun ColorPickerSheet(
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = "#${Integer.toHexString(currentColor.toArgb()).uppercase()}",
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
style = LegadoTheme.typography.bodyLarge
|
||||
)
|
||||
|
||||
Row(
|
||||
@@ -178,14 +180,14 @@ fun ColorPickerSheet(
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
AppText(stringResource(R.string.cancel))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button(onClick = {
|
||||
onColorSelected(currentColor.toArgb())
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
AppText(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -22,6 +20,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@Composable
|
||||
fun TextListInputDialog(
|
||||
@@ -36,22 +36,22 @@ fun TextListInputDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(title) },
|
||||
title = { AppText(title) },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
label = { Text(hint) },
|
||||
label = { AppText(hint) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
if (suggestions.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = "建议:",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
LazyRow(
|
||||
@@ -61,7 +61,7 @@ fun TextListInputDialog(
|
||||
items(suggestions) { suggestion ->
|
||||
AssistChip(
|
||||
onClick = { text = suggestion },
|
||||
label = { Text(suggestion) }
|
||||
label = { AppText(suggestion) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -72,12 +72,12 @@ fun TextListInputDialog(
|
||||
TextButton(
|
||||
onClick = { onConfirm(text) }
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
AppText(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -14,26 +13,39 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
|
||||
@Composable
|
||||
fun PillDivider(
|
||||
modifier: Modifier = Modifier,
|
||||
thickness: Dp = 2.dp,
|
||||
widthFraction: Float = 0.2f,
|
||||
color: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
|
||||
color: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.6f)
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
if (isMiuix) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(widthFraction)
|
||||
.height(thickness)
|
||||
.clip(CircleShape)
|
||||
.background(color)
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
.background(MiuixTheme.colorScheme.secondaryContainer)
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(widthFraction)
|
||||
.height(thickness)
|
||||
.clip(CircleShape)
|
||||
.background(color)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -17,14 +15,16 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
@Composable
|
||||
fun PillHeaderDivider(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
dotSize: Dp = 2.dp,
|
||||
dotColor: Color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
|
||||
titleColor: Color = MaterialTheme.colorScheme.primary
|
||||
dotColor: Color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.8f),
|
||||
titleColor: Color = LegadoTheme.colorScheme.primary
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
@@ -40,9 +40,9 @@ fun PillHeaderDivider(
|
||||
.background(dotColor)
|
||||
)
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
style = LegadoTheme.typography.labelSmall,
|
||||
color = titleColor,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
maxLines = 1
|
||||
|
||||
+15
-19
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||
@@ -17,10 +16,8 @@ import androidx.compose.material.icons.filled.CloudUpload
|
||||
import androidx.compose.material.icons.filled.EditNote
|
||||
import androidx.compose.material.icons.filled.FolderOpen
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -28,7 +25,10 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
|
||||
enum class FilePickerSheetMode {
|
||||
DIR, FILE, EXPORT
|
||||
@@ -37,6 +37,7 @@ enum class FilePickerSheetMode {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FilePickerSheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
title: String = stringResource(R.string.select_operation),
|
||||
onSelectSysDir: (() -> Unit)? = null,
|
||||
@@ -46,22 +47,17 @@ fun FilePickerSheet(
|
||||
onUpload: (() -> Unit)? = null,
|
||||
allowExtensions: Array<String>? = null,
|
||||
) {
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
title = title,
|
||||
onDismissRequest = onDismissRequest,
|
||||
containerColor = { it.surface }
|
||||
containerColor = LegadoTheme.colorScheme.surface
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
)
|
||||
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
@@ -126,7 +122,7 @@ private fun RowScope.FilePickerOptionCard(
|
||||
.weight(1f)
|
||||
.height(100.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
color = LegadoTheme.colorScheme.surfaceContainerLow,
|
||||
tonalElevation = 2.dp
|
||||
) {
|
||||
Column(
|
||||
@@ -134,16 +130,16 @@ private fun RowScope.FilePickerOptionCard(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
AppIcon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
tint = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
AppText(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
|
||||
+10
-9
@@ -25,7 +25,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedIconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -41,7 +40,9 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.button.AnimatedActionButton
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,7 @@ fun HeatmapCalendarTopBar(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("时间线", style = MaterialTheme.typography.titleMedium)
|
||||
AppText("时间线", style = LegadoTheme.typography.titleMedium)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
AnimatedActionButton(
|
||||
@@ -107,7 +108,7 @@ fun WeekdayLabelsColumn(
|
||||
|
||||
labels.forEachIndexed { index, label ->
|
||||
if (index % 2 == 0) {
|
||||
Text(
|
||||
AppText(
|
||||
text = label,
|
||||
fontSize = 10.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -170,7 +171,7 @@ fun NoEarlierDataIndicator(
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
"没有更早数据".forEach { char ->
|
||||
Text(
|
||||
AppText(
|
||||
text = char.toString(),
|
||||
fontSize = 9.sp,
|
||||
lineHeight = 12.sp,
|
||||
@@ -252,7 +253,7 @@ fun HeatmapWeekColumn(
|
||||
|
||||
// 月份标签
|
||||
if (firstDayOfMonth != null) {
|
||||
Text(
|
||||
AppText(
|
||||
text = "${firstDayOfMonth.monthValue}月",
|
||||
fontSize = 10.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -280,9 +281,9 @@ fun HeatmapLegend(
|
||||
) {
|
||||
val legendUnit = if (mode == HeatmapMode.COUNT) "次" else "长"
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
"少($legendUnit)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = Color.Gray
|
||||
)
|
||||
|
||||
@@ -298,9 +299,9 @@ fun HeatmapLegend(
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
AppText(
|
||||
"多($legendUnit)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package io.legado.app.ui.widget.components.icon
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.paint
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.geometry.isUnspecified
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
|
||||
/**
|
||||
* 1. 基础 AppIcon 实现
|
||||
*/
|
||||
@Composable
|
||||
fun AppIcon(
|
||||
painter: Painter,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
tint: Color = Color.Unspecified,
|
||||
) {
|
||||
val defaultTintColor = LegadoTheme.colorScheme.onSurface
|
||||
val finalTint = tint.takeOrElse { defaultTintColor }
|
||||
|
||||
val colorFilter = remember(finalTint) {
|
||||
if (finalTint == Color.Unspecified) null else ColorFilter.tint(finalTint)
|
||||
}
|
||||
|
||||
val semantics = if (contentDescription != null) {
|
||||
Modifier.semantics {
|
||||
this.contentDescription = contentDescription
|
||||
this.role = Role.Image
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier
|
||||
.defaultIconSize(painter)
|
||||
.paint(painter, colorFilter = colorFilter, contentScale = ContentScale.Fit)
|
||||
.then(semantics)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppIcon(
|
||||
imageVector: ImageVector?,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
tint: Color = Color.Unspecified,
|
||||
) {
|
||||
if (imageVector == null) return
|
||||
|
||||
AppIcon(
|
||||
painter = rememberVectorPainter(imageVector),
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
tint = tint,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 辅助扩展函数:处理图标的默认尺寸
|
||||
* M3 和 MIUIX 内部都有 defaultSizeFor,如果 painter 没有固有尺寸,默认设为 24.dp
|
||||
*/
|
||||
private fun Modifier.defaultIconSize(painter: Painter): Modifier {
|
||||
return this.then(
|
||||
if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isUnspecified) {
|
||||
Modifier.size(24.dp)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.legado.app.ui.widget.components.icon
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.Replay
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.basic.Search
|
||||
import top.yukonga.miuix.kmp.icon.extended.Back
|
||||
import top.yukonga.miuix.kmp.icon.extended.Close
|
||||
import top.yukonga.miuix.kmp.icon.extended.Refresh
|
||||
|
||||
/**
|
||||
* 应用全局图标映射层
|
||||
*/
|
||||
object AppIcons {
|
||||
|
||||
private val isMiuix: Boolean
|
||||
@Composable
|
||||
get() = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
val Search: ImageVector
|
||||
@Composable
|
||||
get() = if (isMiuix) {
|
||||
MiuixIcons.Basic.Search
|
||||
} else {
|
||||
Icons.Default.Search
|
||||
}
|
||||
|
||||
val Close: ImageVector
|
||||
@Composable
|
||||
get() = if (isMiuix) {
|
||||
MiuixIcons.Close
|
||||
} else {
|
||||
Icons.Default.Clear
|
||||
}
|
||||
|
||||
val Back: ImageVector
|
||||
@Composable
|
||||
get() = if (isMiuix) {
|
||||
MiuixIcons.Back
|
||||
} else {
|
||||
Icons.AutoMirrored.Filled.ArrowBack
|
||||
}
|
||||
|
||||
val Replay: ImageVector
|
||||
@Composable
|
||||
get() = if (isMiuix) MiuixIcons.Refresh ?: Icons.Default.Replay else Icons.Default.Replay
|
||||
}
|
||||
+83
-117
@@ -1,10 +1,6 @@
|
||||
package io.legado.app.ui.widget.components.importComponents
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -26,26 +22,18 @@ import androidx.compose.material.icons.filled.SelectAll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedToggleButton
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -59,9 +47,13 @@ import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.AnimatedText
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.button.SmallIconButton
|
||||
import io.legado.app.ui.widget.components.card.SelectionItemCard
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AnimatedText
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@@ -77,25 +69,25 @@ fun SourceInputDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(title) },
|
||||
title = { AppText(title) },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
label = { Text(hint) },
|
||||
label = { AppText(hint) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 5
|
||||
)
|
||||
|
||||
if (historyValues.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text("历史记录:", style = MaterialTheme.typography.labelSmall)
|
||||
AppText("历史记录:", style = LegadoTheme.typography.labelSmall)
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(historyValues) { history ->
|
||||
AssistChip(
|
||||
onClick = { text = history },
|
||||
label = { Text(history, maxLines = 1) }
|
||||
label = { AppText(history, maxLines = 1) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -105,41 +97,52 @@ fun SourceInputDialog(
|
||||
confirmButton = {
|
||||
OutlinedButton(
|
||||
onClick = { if (text.isNotBlank()) onConfirm(text) }
|
||||
) { Text(stringResource(android.R.string.ok)) }
|
||||
) { AppText(stringResource(android.R.string.ok)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
AppText(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
//TODO: 动画
|
||||
@SuppressLint("ConfigurationScreenWidthHeight")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T> BatchImportDialog(
|
||||
title: String,
|
||||
importState: BaseImportUiState.Success<T>,
|
||||
importState: BaseImportUiState<T>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (List<T>) -> Unit, // 返回选中的原始数据列表
|
||||
onConfirm: (List<T>) -> Unit,
|
||||
onToggleItem: (index: Int) -> Unit,
|
||||
onToggleAll: (isSelected: Boolean) -> Unit,
|
||||
onItemInfoClick: (index: Int) -> Unit = {},
|
||||
// 插槽:允许调用方自定义顶部菜单
|
||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||
// 插槽:自定义每一行的显示内容
|
||||
itemContent: @Composable (data: T, status: ImportStatus) -> Unit
|
||||
itemTitle: (data: T) -> String,
|
||||
itemSubtitle: (data: T) -> String? = { null }
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val show = importState is BaseImportUiState.Success<T>
|
||||
|
||||
var cachedState by remember { mutableStateOf<BaseImportUiState.Success<T>?>(null) }
|
||||
if (importState is BaseImportUiState.Success<T>) {
|
||||
cachedState = importState
|
||||
}
|
||||
|
||||
if (!show && cachedState == null) return
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val selectedCount = importState.items.count { it.isSelected }
|
||||
val totalCount = importState.items.size
|
||||
|
||||
GlassModalBottomSheet(
|
||||
val currentState = cachedState!!
|
||||
val selectedCount = currentState.items.count { it.isSelected }
|
||||
val totalCount = currentState.items.size
|
||||
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
containerColor = { it.surfaceContainer }
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainer
|
||||
) {
|
||||
AppScaffold(
|
||||
modifier = Modifier
|
||||
@@ -170,44 +173,34 @@ fun <T> BatchImportDialog(
|
||||
ImportBottomBar(
|
||||
selectedCount = selectedCount,
|
||||
totalCount = totalCount,
|
||||
onToggleSelectAll = { isAll -> onToggleAll(isAll) },
|
||||
onToggleSelectAll = onToggleAll,
|
||||
onConfirm = {
|
||||
scope.launch { sheetState.hide() }.invokeOnCompletion {
|
||||
if (!sheetState.isVisible) {
|
||||
val selectedData =
|
||||
importState.items.filter { it.isSelected }.map { it.data }
|
||||
onConfirm(selectedData)
|
||||
}
|
||||
}
|
||||
val selectedData =
|
||||
currentState.items.filter { it.isSelected }.map { it.data }
|
||||
onConfirm(selectedData)
|
||||
},
|
||||
onCancel = {
|
||||
scope.launch { sheetState.hide() }.invokeOnCompletion {
|
||||
if (!sheetState.isVisible) {
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
onCancel = onDismissRequest
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(padding),
|
||||
modifier = Modifier.padding(padding),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
itemsIndexed(importState.items, key = { _, item -> item.data.hashCode() }) { index, itemWrapper ->
|
||||
itemsIndexed(
|
||||
currentState.items,
|
||||
key = { _, item -> item.data.hashCode() }) { index, itemWrapper ->
|
||||
ImportItemRow(
|
||||
title = itemTitle(itemWrapper.data),
|
||||
subtitle = itemSubtitle(itemWrapper.data),
|
||||
isSelected = itemWrapper.isSelected,
|
||||
status = itemWrapper.status,
|
||||
onClick = { onToggleItem(index) },
|
||||
onInfoClick = {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("其实还没写桀桀桀")
|
||||
}
|
||||
scope.launch { snackbarHostState.showSnackbar("其实还没写桀桀桀") }
|
||||
onItemInfoClick(index)
|
||||
},
|
||||
content = { itemContent(itemWrapper.data, itemWrapper.status) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -215,74 +208,47 @@ fun <T> BatchImportDialog(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ImportItemRow(
|
||||
fun ImportItemRow(
|
||||
title: String,
|
||||
subtitle: String? = null,
|
||||
isSelected: Boolean,
|
||||
status: ImportStatus,
|
||||
onClick: () -> Unit,
|
||||
onInfoClick: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
onInfoClick: () -> Unit
|
||||
) {
|
||||
val containerColor by animateColorAsState(
|
||||
targetValue = if (isSelected)
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
else
|
||||
MaterialTheme.colorScheme.surface,
|
||||
animationSpec = tween(
|
||||
durationMillis = 200,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
label = "CardColor"
|
||||
)
|
||||
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = containerColor
|
||||
)
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
headlineContent = { content() },
|
||||
leadingContent = {
|
||||
Checkbox(
|
||||
checked = isSelected,
|
||||
onCheckedChange = null
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = when (status) {
|
||||
ImportStatus.New -> "新增"
|
||||
ImportStatus.Update -> "更新"
|
||||
ImportStatus.Existing -> "已有"
|
||||
ImportStatus.Error -> "错误"
|
||||
},
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = when (status) {
|
||||
ImportStatus.New -> MaterialTheme.colorScheme.primary
|
||||
ImportStatus.Update -> MaterialTheme.colorScheme.secondary
|
||||
ImportStatus.Error -> MaterialTheme.colorScheme.error
|
||||
else -> MaterialTheme.colorScheme.outline
|
||||
}
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
IconButton(onClick = onInfoClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = "详情"
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = ListItemDefaults.colors(
|
||||
containerColor = Color.Transparent
|
||||
SelectionItemCard(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
isSelected = isSelected,
|
||||
inSelectionMode = true,
|
||||
onToggleSelection = onClick,
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
trailingAction = {
|
||||
AppText(
|
||||
text = when (status) {
|
||||
ImportStatus.New -> "新增"
|
||||
ImportStatus.Update -> "更新"
|
||||
ImportStatus.Existing -> "已有"
|
||||
ImportStatus.Error -> "错误"
|
||||
},
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
color = when (status) {
|
||||
ImportStatus.New -> MaterialTheme.colorScheme.primary
|
||||
ImportStatus.Update -> MaterialTheme.colorScheme.secondary
|
||||
ImportStatus.Error -> MaterialTheme.colorScheme.error
|
||||
else -> MaterialTheme.colorScheme.outline
|
||||
},
|
||||
modifier = Modifier.padding(end = 4.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
SmallIconButton(
|
||||
onClick = onInfoClick,
|
||||
icon = Icons.Default.Info,
|
||||
contentDescription = "详情"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -315,12 +281,12 @@ fun ImportBottomBar(
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = onCancel) { Text("取消") }
|
||||
OutlinedButton(onClick = onCancel) { AppText("取消") }
|
||||
Button(
|
||||
enabled = selectedCount > 0,
|
||||
onClick = onConfirm
|
||||
) {
|
||||
Text("导入")
|
||||
AppText("导入")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -46,6 +46,7 @@ import androidx.compose.ui.util.fastFirstOrNull
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastLastOrNull
|
||||
import androidx.compose.ui.util.fastMaxBy
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -67,7 +68,7 @@ fun VerticalFastScroller(
|
||||
listState: LazyListState,
|
||||
modifier: Modifier = Modifier,
|
||||
thumbAllowed: () -> Boolean = { true },
|
||||
thumbColor: Color = MaterialTheme.colorScheme.primary,
|
||||
thumbColor: Color = LegadoTheme.colorScheme.primary,
|
||||
topContentPadding: Dp = Dp.Hairline,
|
||||
bottomContentPadding: Dp = Dp.Hairline,
|
||||
endContentPadding: Dp = Dp.Hairline,
|
||||
|
||||
@@ -15,19 +15,11 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -35,11 +27,13 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import io.legado.app.ui.widget.components.AppFloatingActionButton
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.SelectionActions
|
||||
import io.legado.app.ui.widget.components.SelectionBottomBar
|
||||
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarScrollBehavior
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -52,28 +46,21 @@ fun <T> ListScaffold(
|
||||
onSearchQueryChange: (String) -> Unit,
|
||||
searchPlaceholder: String = "搜索...",
|
||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||
bottomContent: @Composable (ColumnScope.(TopAppBarScrollBehavior) -> Unit)? = null,
|
||||
bottomContent: @Composable (ColumnScope.(GlassTopAppBarScrollBehavior) -> Unit)? = null,
|
||||
dropDownMenuContent: @Composable (ColumnScope.(dismiss: () -> Unit) -> Unit)? = null,
|
||||
selectionActions: SelectionActions? = null,
|
||||
onAddClick: (() -> Unit)? = null,
|
||||
floatingActionButton: @Composable () -> Unit = {
|
||||
onAddClick?.let { onClick ->
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
|
||||
TooltipAnchorPosition.Above
|
||||
AppFloatingActionButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.animateFloatingActionButton(
|
||||
visible = state.selectedIds.isEmpty(),
|
||||
alignment = Alignment.BottomEnd,
|
||||
),
|
||||
tooltip = { PlainTooltip { Text("添加") } },
|
||||
state = rememberTooltipState(),
|
||||
tooltipText = "添加"
|
||||
) {
|
||||
FloatingActionButton(
|
||||
modifier = Modifier.animateFloatingActionButton(
|
||||
visible = state.selectedIds.isEmpty(),
|
||||
alignment = Alignment.BottomEnd,
|
||||
),
|
||||
onClick = onClick
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Add")
|
||||
}
|
||||
Icon(Icons.Default.Add, contentDescription = "Add")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+42
-18
@@ -3,6 +3,8 @@ package io.legado.app.ui.widget.components.menuItem
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialExpressiveTheme
|
||||
@@ -15,8 +17,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.theme.rememberOpaqueColorScheme
|
||||
|
||||
import top.yukonga.miuix.kmp.basic.ListPopupColumn
|
||||
import top.yukonga.miuix.kmp.extra.SuperListPopup
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -24,31 +29,50 @@ fun RoundDropdownMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
// M3 专属视觉参数,Miuix 模式下会优雅降级(忽略)
|
||||
shape: Shape = MaterialTheme.shapes.medium,
|
||||
shadowElevation: Dp = 4.dp,
|
||||
verticalSpacing: Dp = 8.dp,
|
||||
content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit
|
||||
) {
|
||||
val colorScheme = rememberOpaqueColorScheme()
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
shadowElevation = shadowElevation,
|
||||
containerColor = colorScheme.surfaceContainerLow
|
||||
) {
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography(),
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
shapes = Shapes()
|
||||
if (isMiuix) {
|
||||
SuperListPopup(
|
||||
show = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
popupModifier = modifier
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
|
||||
ListPopupColumn {
|
||||
Column() {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
content(onDismissRequest)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val colorScheme = rememberOpaqueColorScheme()
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
shape = shape,
|
||||
shadowElevation = shadowElevation,
|
||||
containerColor = colorScheme.surfaceContainerLow
|
||||
) {
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography(),
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
shapes = Shapes()
|
||||
) {
|
||||
content(onDismissRequest)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
|
||||
) {
|
||||
content(onDismissRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+90
-24
@@ -1,6 +1,8 @@
|
||||
package io.legado.app.ui.widget.components.menuItem
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -11,53 +13,114 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MenuDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.theme.rememberOpaqueColorScheme
|
||||
import io.legado.app.ui.widget.components.icon.AppIcon
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.basic.Text as MiuixText
|
||||
|
||||
@Composable
|
||||
fun RoundDropdownMenuItem(
|
||||
text: @Composable () -> Unit,
|
||||
text: String,
|
||||
color: Color = LegadoTheme.colorScheme.surface,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isSelected: Boolean = false,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
enabled: Boolean = true,
|
||||
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
) {
|
||||
val colorScheme = rememberOpaqueColorScheme()
|
||||
val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)
|
||||
val interaction = interactionSource ?: remember { MutableInteractionSource() }
|
||||
|
||||
val contentColor =
|
||||
if (enabled) MaterialTheme.colorScheme.onSurface
|
||||
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||
if (isMiuix) {
|
||||
val backgroundColor = if (isSelected) {
|
||||
LegadoTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
enabled = enabled,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorScheme.surface,
|
||||
interactionSource = interaction
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides contentColor,
|
||||
LocalTextStyle provides MaterialTheme.typography.labelLarge
|
||||
val contentColor = if (isSelected) {
|
||||
LegadoTheme.colorScheme.onPrimaryContainer
|
||||
} else {
|
||||
LegadoTheme.colorScheme.onSurface
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.drawBehind { drawRect(backgroundColor) }
|
||||
.clickable(
|
||||
interactionSource = interaction,
|
||||
indication = androidx.compose.foundation.LocalIndication.current,
|
||||
enabled = enabled,
|
||||
onClick = onClick
|
||||
)
|
||||
.padding(horizontal = 20.dp, vertical = 12.dp)
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides contentColor,
|
||||
LocalTextStyle provides MiuixTheme.textStyles.body1
|
||||
) {
|
||||
if (leadingIcon != null) {
|
||||
leadingIcon()
|
||||
Spacer(Modifier.width(12.dp))
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
MiuixText(
|
||||
text = text,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
|
||||
if (trailingIcon != null) {
|
||||
Spacer(Modifier.width(12.dp))
|
||||
trailingIcon()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val colorScheme = rememberOpaqueColorScheme()
|
||||
val contentColor = if (enabled) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||
}
|
||||
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
enabled = enabled,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorScheme.surface,
|
||||
interactionSource = interaction
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -66,17 +129,20 @@ fun RoundDropdownMenuItem(
|
||||
.widthIn(min = 120.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
if (leadingIcon != null) {
|
||||
leadingIcon()
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Spacer(Modifier.width(12.dp))
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
text()
|
||||
Text(
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
text = text,
|
||||
style = LegadoTheme.typography.bodyMediumEmphasized
|
||||
)
|
||||
}
|
||||
|
||||
if (trailingIcon != null) {
|
||||
@@ -93,9 +159,9 @@ fun MenuItemIcon(
|
||||
imageVector: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null,
|
||||
tint: Color = LocalContentColor.current
|
||||
tint: Color = Color.Unspecified
|
||||
) {
|
||||
Icon(
|
||||
AppIcon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.size(18.dp),
|
||||
|
||||
+138
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package io.legado.app.ui.widget.components.modalBottomSheet
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialExpressiveTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.MotionScheme
|
||||
import androidx.compose.material3.Shapes
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalWindowInfo
|
||||
import io.legado.app.ui.theme.rememberOpaqueColorScheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun GlassModalBottomSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
containerColor: (ColorScheme) -> Color = { it.surfaceContainerLow },
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
val colorScheme = rememberOpaqueColorScheme()
|
||||
val density = LocalDensity.current
|
||||
val maxHeight = with(density) {
|
||||
LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
|
||||
}
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
sheetState = sheetState,
|
||||
containerColor = containerColor(colorScheme),
|
||||
contentColor = colorScheme.onSurface
|
||||
) {
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography(),
|
||||
motionScheme = MotionScheme.expressive(),
|
||||
shapes = Shapes()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = maxHeight)
|
||||
.animateContentSize()
|
||||
.then(modifier),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -36,7 +35,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -52,6 +52,7 @@ data class RuleEditFields(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T> RuleEditSheet(
|
||||
show: Boolean,
|
||||
rule: T?,
|
||||
title: String,
|
||||
label1: String,
|
||||
@@ -74,7 +75,8 @@ fun <T> RuleEditSheet(
|
||||
|
||||
fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
|
||||
|
||||
GlassModalBottomSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
@@ -84,7 +86,7 @@ fun <T> RuleEditSheet(
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
CenterAlignedTopAppBar(
|
||||
title = { Text(title) },
|
||||
title = { AppText(title) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
@@ -99,7 +101,7 @@ fun <T> RuleEditSheet(
|
||||
}
|
||||
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.copy_rule)) },
|
||||
text = { AppText(stringResource(R.string.copy_rule)) },
|
||||
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
|
||||
onClick = {
|
||||
onCopy(getCurrentEntity())
|
||||
@@ -107,7 +109,7 @@ fun <T> RuleEditSheet(
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.paste_rule)) },
|
||||
text = { AppText(stringResource(R.string.paste_rule)) },
|
||||
leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -136,20 +138,20 @@ fun <T> RuleEditSheet(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text(stringResource(R.string.name)) },
|
||||
label = { AppText(stringResource(R.string.name)) },
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = rule1,
|
||||
onValueChange = { rule1 = it },
|
||||
label = { Text(label1) }
|
||||
label = { AppText(label1) }
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = rule2,
|
||||
onValueChange = { rule2 = it },
|
||||
label = { Text(label2) },
|
||||
label = { AppText(label2) },
|
||||
minLines = 3
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user