[优化] 界面优化

This commit is contained in:
HapeLee
2026-04-11 17:25:09 +08:00
parent 42dda827bc
commit 1d585ecec9
41 changed files with 1041 additions and 451 deletions
-10
View File
@@ -274,16 +274,6 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:launchMode="singleTop" /> android:launchMode="singleTop" />
<!-- 导入书籍 -->
<activity
android:name=".ui.book.import.local.ImportBookActivity"
android:enableOnBackInvokedCallback="true"
android:launchMode="singleTop" />
<!-- 添加远程 -->
<activity
android:name=".ui.book.import.remote.RemoteBookActivity"
android:enableOnBackInvokedCallback="true"
android:launchMode="singleTop" />
<!-- 发现界面 --> <!-- 发现界面 -->
<activity <activity
android:name=".ui.book.explore.ExploreShowActivity" android:name=".ui.book.explore.ExploreShowActivity"
@@ -22,6 +22,7 @@ import io.legado.app.help.http.okHttpClientManga
import io.legado.app.ui.book.bookmark.AllBookmarkViewModel import io.legado.app.ui.book.bookmark.AllBookmarkViewModel
import io.legado.app.ui.book.explore.ExploreShowViewModel import io.legado.app.ui.book.explore.ExploreShowViewModel
import io.legado.app.ui.book.group.GroupViewModel import io.legado.app.ui.book.group.GroupViewModel
import io.legado.app.ui.book.import.local.ImportBookViewModel
import io.legado.app.ui.book.import.remote.RemoteBookViewModel import io.legado.app.ui.book.import.remote.RemoteBookViewModel
import io.legado.app.ui.book.info.BookInfoViewModel import io.legado.app.ui.book.info.BookInfoViewModel
import io.legado.app.ui.book.readRecord.ReadRecordViewModel import io.legado.app.ui.book.readRecord.ReadRecordViewModel
@@ -97,6 +98,7 @@ val appModule = module {
viewModelOf(::ThemeConfigViewModel) viewModelOf(::ThemeConfigViewModel)
viewModelOf(::BackupConfigViewModel) viewModelOf(::BackupConfigViewModel)
viewModelOf(::TocViewModel) viewModelOf(::TocViewModel)
viewModelOf(::ImportBookViewModel)
viewModelOf(::RemoteBookViewModel) viewModelOf(::RemoteBookViewModel)
viewModelOf(::BookInfoViewModel) viewModelOf(::BookInfoViewModel)
viewModelOf(::ExploreViewModel) viewModelOf(::ExploreViewModel)
@@ -49,6 +49,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
@@ -242,7 +243,7 @@ fun AllBookmarkScreen(
FastScrollLazyColumn( FastScrollLazyColumn(
state = listState, state = listState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = adaptiveContentPaddingOnlyVertical( contentPadding = adaptiveContentPadding(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = 120.dp bottom = 120.dp
) )
@@ -235,7 +235,6 @@ fun ExploreShowScreen(
AppModalBottomSheet( AppModalBottomSheet(
show = showKindSheet, show = showKindSheet,
containerColor = LegadoTheme.colorScheme.surface,
onDismissRequest = { showKindSheet = false } onDismissRequest = { showKindSheet = false }
) { ) {
@@ -1,163 +0,0 @@
package io.legado.app.ui.book.import
import android.os.Bundle
import android.view.MotionEvent
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.ViewModel
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.databinding.ActivityImportBookBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector
//import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.model.localBook.LocalBook
import io.legado.app.ui.file.HandleFileContract
import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.FileDoc
import io.legado.app.utils.hideSoftInput
import io.legado.app.utils.shouldHideSoftInput
import io.legado.app.utils.startActivityForBook
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
abstract class BaseImportBookActivity<VM : ViewModel> :
VMBaseActivity<ActivityImportBookBinding, VM>() {
final override val binding by viewBinding(ActivityImportBookBinding::inflate)
private var localBookTreeSelectListener: ((Boolean) -> Unit)? = null
protected val searchView: SearchView by lazy {
binding.titleBar.findViewById(R.id.search_view)
}
val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
it.uri?.let { treeUri ->
AppConfig.defaultBookTreeUri = treeUri.toString()
localBookTreeSelectListener?.invoke(true)
} ?: localBookTreeSelectListener?.invoke(false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initSearchView()
}
/**
* 设置书籍保存位置
*/
protected suspend fun setBookStorage() = suspendCoroutine { block ->
localBookTreeSelectListener = {
localBookTreeSelectListener = null
block.resume(it)
}
//测试书籍保存位置是否设置
if (!AppConfig.defaultBookTreeUri.isNullOrBlank()) {
localBookTreeSelectListener = null
block.resume(true)
return@suspendCoroutine
}
//测试读写??
val storageHelp = String(assets.open("storageHelp.md").readBytes())
val hint = getString(R.string.select_book_folder)
alert(hint, storageHelp) {
okButton {
localBookTreeSelect.launch {
title = hint
}
}
cancelButton {
localBookTreeSelectListener = null
block.resume(false)
}
onCancelled {
localBookTreeSelectListener = null
block.resume(false)
}
}
}
abstract fun onSearchTextChange(newText: String?)
protected fun startReadBook(book: Book) {
startActivityForBook(book)
}
protected fun onArchiveFileClick(fileDoc: FileDoc) {
val fileNames = ArchiveUtils.getArchiveFilesName(fileDoc) {
it.matches(AppPattern.bookFileRegex)
}
if (fileNames.size == 1) {
val name = fileNames[0]
appDb.bookDao.getBookByFileName(name)?.let {
startReadBook(it)
} ?: showImportAlert(fileDoc, name)
} else {
showSelectBookReadAlert(fileDoc, fileNames)
}
}
private fun showSelectBookReadAlert(fileDoc: FileDoc, fileNames: List<String>) {
if (fileNames.isEmpty()) {
toastOnUi(R.string.unsupport_archivefile_entry)
return
}
selector(
R.string.start_read,
fileNames
) { _, name, _ ->
appDb.bookDao.getBookByFileName(name)?.let {
startReadBook(it)
} ?: showImportAlert(fileDoc, name)
}
}
/* 添加压缩包内指定文件到书架 */
private inline fun addArchiveToBookShelf(
fileDoc: FileDoc,
fileName: String,
onSuccess: (Book) -> Unit
) {
LocalBook.importArchiveFile(fileDoc.uri, fileName) {
it.contains(fileName)
}.firstOrNull()?.run {
onSuccess.invoke(this)
}
}
/* 提示是否重新导入所点击的压缩文件 */
private fun showImportAlert(fileDoc: FileDoc, fileName: String) {
alert(
R.string.draw,
R.string.no_book_found_bookshelf
) {
okButton {
addArchiveToBookShelf(fileDoc, fileName) {
startReadBook(it)
}
}
noButton()
}
}
private fun initSearchView() {
//searchView.applyTint(primaryTextColor)
searchView.isSubmitButtonEnabled = true
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
onSearchTextChange(newText)
return false
}
})
}
}
@@ -0,0 +1,497 @@
package io.legado.app.ui.book.import.local
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.CloudDownload
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.outlined.Book
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.model.localBook.LocalBook
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
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.button.TopBarActionButton
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.filePicker.FilePickerSheet
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.text.AppText
import io.legado.app.utils.ConvertUtils
import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.FileDoc
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.isUri
import io.legado.app.utils.startActivityForBook
import io.legado.app.utils.takePersistablePermissionSafely
import io.legado.app.utils.toastOnUi
import org.koin.androidx.compose.koinViewModel
import java.io.File
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ImportBookContent(
state: ImportBookUiState,
onBackClick: () -> Unit,
onSearchToggle: (Boolean) -> Unit,
onSearchQueryChange: (String) -> Unit,
onSelectFolder: () -> Unit,
onScanFolder: () -> Unit,
onImportFileName: () -> Unit,
onSortChange: (Int) -> Unit,
onNavigateBack: () -> Unit,
onNavigateToLevel: (Int) -> Unit,
onSelectAll: () -> Unit,
onSelectInvert: () -> Unit,
onAddToBookshelf: () -> Unit,
onDeleteSelection: () -> Unit,
onItemClick: (ImportBook) -> Unit
) {
ListScaffold(
title = stringResource(R.string.local_book),
state = state,
onBackClick = onBackClick,
onSearchToggle = onSearchToggle,
onSearchQueryChange = onSearchQueryChange,
searchPlaceholder = stringResource(R.string.screen),
topBarActions = {
TopBarActionButton(
onClick = onSelectFolder,
imageVector = Icons.Default.FolderOpen,
contentDescription = stringResource(R.string.select_folder)
)
},
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
text = stringResource(R.string.sort_by_name),
onClick = {
onSortChange(0)
dismiss()
},
trailingIcon = {
if (state.sort == 0) {
Icon(Icons.Default.Check, null)
}
}
)
RoundDropdownMenuItem(
text = stringResource(R.string.sort_by_size),
onClick = {
onSortChange(1)
dismiss()
},
trailingIcon = {
if (state.sort == 1) {
Icon(Icons.Default.Check, null)
}
}
)
RoundDropdownMenuItem(
text = stringResource(R.string.sort_by_time),
onClick = {
onSortChange(2)
dismiss()
},
trailingIcon = {
if (state.sort == 2) {
Icon(Icons.Default.Check, null)
}
}
)
RoundDropdownMenuItem(
text = stringResource(R.string.scan_folder),
onClick = {
onScanFolder()
dismiss()
}
)
RoundDropdownMenuItem(
text = stringResource(R.string.import_file_name),
onClick = {
onImportFileName()
dismiss()
}
)
},
bottomContent = {
ImportPathNavigationBar(
pathNames = state.pathNames,
canGoBack = state.canGoBack,
onNavigateBack = onNavigateBack,
onNavigateToLevel = onNavigateToLevel
)
},
selectionActions = SelectionActions(
onSelectAll = onSelectAll,
onSelectInvert = onSelectInvert,
primaryAction = ActionItem(
text = stringResource(R.string.add_to_bookshelf),
icon = { Icon(Icons.Default.CloudDownload, null) },
onClick = onAddToBookshelf
),
secondaryActions = listOf(
ActionItem(
text = stringResource(R.string.delete),
icon = { Icon(Icons.Default.Delete, null) },
onClick = onDeleteSelection
)
)
),
onAddClick = null
) { paddingValues ->
val refreshState = rememberPullToRefreshState()
PullToRefreshBox(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
isRefreshing = state.isLoading,
state = refreshState,
onRefresh = onScanFolder
) {
when {
state.items.isEmpty() && state.isLoading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
state.items.isEmpty() -> {
EmptyMessageView(
modifier = Modifier.fillMaxSize(),
message = stringResource(R.string.empty_msg_import_book)
)
}
else -> {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.items, key = { it.selectionId }) { item ->
ImportBookItem(
modifier = Modifier.animateItem(),
item = item,
isSelected = item.selectionId in state.selectedIds,
onClick = { onItemClick(item) }
)
}
}
}
}
}
}
}
@Composable
fun ImportBookScreen(
onBackClick: () -> Unit,
viewModel: ImportBookViewModel = koinViewModel()
) {
val context = LocalContext.current
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
var showFolderPicker by remember { mutableStateOf(false) }
var showImportFileNameDialog by remember { mutableStateOf(false) }
var fileNameJs by remember { mutableStateOf(AppConfig.bookImportFileName.orEmpty()) }
var pickerTarget by remember { mutableStateOf(ImportFolderPickTarget.IMPORT_FOLDER) }
val selectDocTree = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree()
) { uri ->
viewModel.dispatch(ImportBookIntent.FolderPicked(uri, pickerTarget))
}
LaunchedEffect(viewModel) {
viewModel.dispatch(ImportBookIntent.Initialize)
viewModel.effects.collect { effect ->
when (effect) {
is ImportBookEffect.RequestFolderPicker -> {
pickerTarget = effect.target
selectDocTree.launch(effect.initialUri)
}
is ImportBookEffect.OpenBook -> {
context.startActivityForBook(effect.book)
}
is ImportBookEffect.ShowArchiveEntries -> {
context.selector(R.string.start_read, effect.fileNames) { _, name, _ ->
viewModel.dispatch(
ImportBookIntent.ArchiveEntrySelected(effect.fileDoc, name)
)
}
}
is ImportBookEffect.ShowImportArchiveDialog -> {
context.alert(R.string.draw, R.string.no_book_found_bookshelf) {
okButton {
viewModel.dispatch(
ImportBookIntent.ImportArchiveConfirmed(
effect.fileDoc,
effect.fileName
)
)
}
noButton()
}
}
is ImportBookEffect.ShowToastRes -> {
context.toastOnUi(effect.resId)
}
}
}
}
FilePickerSheet(
show = showFolderPicker,
onDismissRequest = { showFolderPicker = false },
title = stringResource(R.string.select_folder),
onSelectSysDir = {
showFolderPicker = false
viewModel.dispatch(ImportBookIntent.SelectFolderClick)
}
)
AppAlertDialog(
show = showImportFileNameDialog,
onDismissRequest = { showImportFileNameDialog = false },
title = stringResource(R.string.import_file_name),
content = {
AppText("Use js to parse file name from src, then assign name/author.")
OutlinedTextField(
value = fileNameJs,
onValueChange = { fileNameJs = it },
label = { AppText("js") }
)
},
confirmText = stringResource(android.R.string.ok),
onConfirm = {
AppConfig.bookImportFileName = fileNameJs
showImportFileNameDialog = false
},
dismissText = stringResource(android.R.string.cancel),
onDismiss = { showImportFileNameDialog = false }
)
ImportBookContent(
state = uiState,
onBackClick = onBackClick,
onSearchToggle = { viewModel.dispatch(ImportBookIntent.SearchToggle(it)) },
onSearchQueryChange = { viewModel.dispatch(ImportBookIntent.SearchQueryChange(it)) },
onSelectFolder = { showFolderPicker = true },
onScanFolder = { viewModel.dispatch(ImportBookIntent.ScanFolder) },
onImportFileName = {
fileNameJs = AppConfig.bookImportFileName.orEmpty()
showImportFileNameDialog = true
},
onSortChange = { viewModel.dispatch(ImportBookIntent.SortChange(it)) },
onNavigateBack = { viewModel.dispatch(ImportBookIntent.NavigateBack) },
onNavigateToLevel = { viewModel.dispatch(ImportBookIntent.NavigateToLevel(it)) },
onSelectAll = { viewModel.dispatch(ImportBookIntent.SelectAll) },
onSelectInvert = { viewModel.dispatch(ImportBookIntent.SelectInvert) },
onAddToBookshelf = { viewModel.dispatch(ImportBookIntent.AddToBookshelf) },
onDeleteSelection = { viewModel.dispatch(ImportBookIntent.DeleteSelection) },
onItemClick = { viewModel.dispatch(ImportBookIntent.ItemClick(it)) }
)
}
@Composable
private fun ImportPathNavigationBar(
pathNames: List<String>,
canGoBack: Boolean,
onNavigateBack: () -> Unit,
onNavigateToLevel: (Int) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp)
.animateContentSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
GlassCard(
modifier = Modifier.weight(1f),
containerColor = LegadoTheme.colorScheme.surfaceContainer
) {
LazyRow(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
itemsIndexed(pathNames) { index, name ->
val isLast = index == pathNames.lastIndex
AppText(
text = name,
style = LegadoTheme.typography.labelSmall,
fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Medium,
color = if (isLast) {
LegadoTheme.colorScheme.primary
} else {
LegadoTheme.colorScheme.onSurfaceVariant
},
modifier = Modifier
.clip(MaterialTheme.shapes.small)
.then(if (!isLast) Modifier.clickable { onNavigateToLevel(index) } else Modifier)
.padding(horizontal = 4.dp, vertical = 4.dp)
)
if (!isLast) {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = null,
modifier = Modifier.size(12.dp),
tint = LegadoTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
}
}
}
}
if (canGoBack) {
SmallTonalIconButton(
onClick = onNavigateBack,
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "back"
)
}
}
}
@Composable
private fun ImportBookItem(
modifier: Modifier,
item: ImportBook,
isSelected: Boolean,
onClick: () -> Unit
) {
val containerColor = if (isSelected) {
LegadoTheme.colorScheme.secondaryContainer
} else {
LegadoTheme.colorScheme.surfaceContainer
}
GlassCard(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp),
onClick = onClick,
containerColor = containerColor
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = when {
item.isDir -> Icons.Default.Folder
item.isOnBookShelf -> Icons.Outlined.Book
else -> Icons.Outlined.Description
},
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = if (item.isDir) {
LegadoTheme.colorScheme.primary
} else {
LegadoTheme.colorScheme.onSurfaceVariant
}
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
AppText(
text = item.name,
style = LegadoTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (!item.isDir) {
Row(verticalAlignment = Alignment.CenterVertically) {
TextCard(
text = item.name.substringAfterLast('.', "").uppercase(),
textStyle = LegadoTheme.typography.labelSmall,
horizontalPadding = 4.dp,
verticalPadding = 2.dp,
cornerRadius = 4.dp,
icon = null,
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHighest
)
Spacer(modifier = Modifier.width(6.dp))
AppText(
text = "${ConvertUtils.formatFileSize(item.size)} - ${AppConst.dateFormat.format(item.lastModified)}",
style = LegadoTheme.typography.labelMedium,
color = LegadoTheme.colorScheme.onSurfaceVariant
)
}
}
}
if (!item.isDir && !item.isOnBookShelf) {
Spacer(modifier = Modifier.width(8.dp))
Icon(
imageVector = if (isSelected) Icons.Default.Check else Icons.Default.ChevronRight,
contentDescription = null,
tint = LegadoTheme.colorScheme.primary
)
}
}
}
}
@@ -145,15 +145,15 @@ fun BookInfoEditContent(
) )
) )
}, },
icon = Icons.Default.ImageSearch imageVector = Icons.Default.ImageSearch
) )
MediumOutlinedIconButton( MediumOutlinedIconButton(
onClick = { selectCover.launch() }, onClick = { selectCover.launch() },
icon = Icons.Default.FolderOpen imageVector = Icons.Default.FolderOpen
) )
MediumOutlinedIconButton( MediumOutlinedIconButton(
onClick = { viewModel.resetCover() }, onClick = { viewModel.resetCover() },
icon = Icons.Default.Replay imageVector = Icons.Default.Replay
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -69,6 +69,8 @@ import cn.hutool.core.date.DateUtil
import io.legado.app.data.entities.readRecord.ReadRecord import io.legado.app.data.entities.readRecord.ReadRecord
import io.legado.app.data.entities.readRecord.ReadRecordDetail import io.legado.app.data.entities.readRecord.ReadRecordDetail
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.CollapsibleHeader import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
@@ -78,9 +80,12 @@ import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.AppIconButton import io.legado.app.ui.widget.components.button.AppIconButton
import io.legado.app.ui.widget.components.button.TopBarNavigationButton import io.legado.app.ui.widget.components.button.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.cover.Cover import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarTopBar import io.legado.app.ui.widget.components.heatmap.HEATMAP_CALENDAR_TITLE
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarEndAction
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarStartAction
import io.legado.app.ui.widget.components.heatmap.HeatmapConfig import io.legado.app.ui.widget.components.heatmap.HeatmapConfig
import io.legado.app.ui.widget.components.heatmap.HeatmapLegend import io.legado.app.ui.widget.components.heatmap.HeatmapLegend
import io.legado.app.ui.widget.components.heatmap.HeatmapMode import io.legado.app.ui.widget.components.heatmap.HeatmapMode
@@ -91,6 +96,7 @@ import io.legado.app.ui.widget.components.heatmap.rememberDateRange
import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange import io.legado.app.ui.widget.components.heatmap.rememberDaysInRange
import io.legado.app.ui.widget.components.heatmap.rememberWeeks import io.legado.app.ui.widget.components.heatmap.rememberWeeks
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.swipe.SwipeAction import io.legado.app.ui.widget.components.swipe.SwipeAction
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
@@ -118,6 +124,7 @@ fun ReadRecordScreen(
val displayMode by viewModel.displayMode.collectAsState() val displayMode by viewModel.displayMode.collectAsState()
var showSearch by remember { mutableStateOf(false) } var showSearch by remember { mutableStateOf(false) }
var showCalendar by remember { mutableStateOf(false) } var showCalendar by remember { mutableStateOf(false) }
var heatmapMode by remember { mutableStateOf(HeatmapMode.COUNT) }
val listState = rememberLazyListState() val listState = rememberLazyListState()
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
@@ -131,6 +138,36 @@ fun ReadRecordScreen(
pendingDeleteAction = action pendingDeleteAction = action
} }
} }
val stickyDate by remember(displayMode, listState) {
derivedStateOf {
if (displayMode == DisplayMode.LATEST) return@derivedStateOf null
val stickyKey = listState.layoutInfo.visibleItemsInfo
.firstOrNull { info ->
val key = info.key.toString()
key.startsWith("header_") ||
key.startsWith("timeline_header_") ||
key.startsWith("agg_item_") ||
key.startsWith("timeline_item_")
}?.key?.toString() ?: return@derivedStateOf null
when {
stickyKey.startsWith("header_") -> stickyKey.removePrefix("header_")
stickyKey.startsWith("timeline_header_") -> stickyKey.removePrefix("timeline_header_")
stickyKey.startsWith("agg_item_") -> stickyKey.removePrefix("agg_item_").substringBefore("|")
stickyKey.startsWith("timeline_item_") -> stickyKey.removePrefix("timeline_item_").substringBefore("|")
else -> null
}
}
}
val floatingDate by remember(stickyDate, listState, displayMode) {
derivedStateOf {
if (displayMode == DisplayMode.LATEST) return@derivedStateOf null
if (stickyDate == null) return@derivedStateOf null
val shouldStick = listState.firstVisibleItemIndex > 1 ||
listState.firstVisibleItemScrollOffset > 24
if (shouldStick) stickyDate else null
}
}
LaunchedEffect(state.searchKey) { LaunchedEffect(state.searchKey) {
if (state.searchKey.isNullOrBlank()) { if (state.searchKey.isNullOrBlank()) {
@@ -198,9 +235,6 @@ fun ReadRecordScreen(
} }
} }
) { padding -> ) { padding ->
Column(modifier = Modifier
.padding(padding)
.fillMaxSize()) {
val contentState = when { val contentState = when {
state.isLoading -> "LOADING" state.isLoading -> "LOADING"
(displayMode == DisplayMode.AGGREGATE && state.groupedRecords.isEmpty()) || (displayMode == DisplayMode.AGGREGATE && state.groupedRecords.isEmpty()) ||
@@ -215,7 +249,12 @@ fun ReadRecordScreen(
when (targetState) { when (targetState) {
"LOADING" -> { "LOADING" -> {
EmptyMessageView( EmptyMessageView(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(
top = padding.calculateTopPadding(),
bottom = padding.calculateBottomPadding()
),
message = "加载中", message = "加载中",
isLoading = true isLoading = true
) )
@@ -223,16 +262,25 @@ fun ReadRecordScreen(
"EMPTY" -> { "EMPTY" -> {
EmptyMessageView( EmptyMessageView(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(
top = padding.calculateTopPadding(),
bottom = padding.calculateBottomPadding()
),
message = "没有记录" message = "没有记录"
) )
} }
"CONTENT" -> { "CONTENT" -> {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.nestedScroll(scrollBehavior.nestedScrollConnection) contentPadding = adaptiveContentPaddingOnlyVertical(
top = padding.calculateTopPadding(),
bottom = padding.calculateBottomPadding() + 16.dp
)
) { ) {
item(key = "summary_card") { item(key = "summary_card") {
SummarySection(state, viewModel) SummarySection(state, viewModel)
@@ -255,6 +303,32 @@ fun ReadRecordScreen(
} }
) )
} }
TopFloatingStickyItem(
item = floatingDate,
modifier = Modifier.padding(
top = padding.calculateTopPadding() + 4.dp,
start = 8.dp
)
) { date ->
val text = buildString {
append(formatFriendlyDate(date))
if (displayMode == DisplayMode.AGGREGATE) {
val dailyTotal = state.groupedRecords[date]?.sumOf { it.readTime } ?: 0L
append(" · ")
append(formatDuring(dailyTotal))
}
}
TextCard(
text = text,
textStyle = LegadoTheme.typography.labelLarge,
backgroundColor = LegadoTheme.colorScheme.cardContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer,
cornerRadius = 8.dp,
horizontalPadding = 8.dp,
verticalPadding = 8.dp
)
}
} }
} }
} }
@@ -292,19 +366,31 @@ fun ReadRecordScreen(
AppModalBottomSheet( AppModalBottomSheet(
show = showCalendar, show = showCalendar,
onDismissRequest = { showCalendar = false } onDismissRequest = { showCalendar = false },
title = HEATMAP_CALENDAR_TITLE,
startAction = {
HeatmapCalendarStartAction(
currentMode = heatmapMode,
onModeChanged = { heatmapMode = it }
)
},
endAction = {
HeatmapCalendarEndAction(
onClearDate = {
viewModel.setSelectedDate(null)
showCalendar = false
}
)
}
) { ) {
HeatmapCalendarSection( HeatmapCalendarSection(
dailyReadCounts = state.dailyReadCounts, dailyReadCounts = state.dailyReadCounts,
dailyReadTimes = state.dailyReadTimes, dailyReadTimes = state.dailyReadTimes,
currentMode = heatmapMode,
selectedDate = state.selectedDate, selectedDate = state.selectedDate,
onDateSelected = { date -> onDateSelected = { date ->
viewModel.setSelectedDate(date) viewModel.setSelectedDate(date)
showCalendar = false showCalendar = false
},
onClearDate = {
viewModel.setSelectedDate(null)
showCalendar = false
} }
) )
} }
@@ -404,13 +490,11 @@ fun HeatmapCalendarSection(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dailyReadCounts: Map<LocalDate, Int>, dailyReadCounts: Map<LocalDate, Int>,
dailyReadTimes: Map<LocalDate, Long>, dailyReadTimes: Map<LocalDate, Long>,
currentMode: HeatmapMode,
selectedDate: LocalDate?, selectedDate: LocalDate?,
onDateSelected: (LocalDate) -> Unit, onDateSelected: (LocalDate) -> Unit,
onClearDate: () -> Unit,
config: HeatmapConfig = HeatmapConfig() config: HeatmapConfig = HeatmapConfig()
) { ) {
var currentMode by remember { mutableStateOf(HeatmapMode.COUNT) }
val (startDate, endDate) = rememberDateRange(dailyReadCounts, dailyReadTimes) val (startDate, endDate) = rememberDateRange(dailyReadCounts, dailyReadTimes)
val days = rememberDaysInRange(startDate, endDate) val days = rememberDaysInRange(startDate, endDate)
val weeks = rememberWeeks(days, startDate) val weeks = rememberWeeks(days, startDate)
@@ -452,14 +536,6 @@ fun HeatmapCalendarSection(
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 8.dp)
.padding(bottom = 32.dp) .padding(bottom = 32.dp)
) { ) {
HeatmapCalendarTopBar(
currentMode = currentMode,
onModeChanged = { currentMode = it },
onClearDate = onClearDate
)
Spacer(modifier = Modifier.height(8.dp))
Row(modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.fillMaxWidth()) {
WeekdayLabelsColumn( WeekdayLabelsColumn(
cellSize = config.cellSize, cellSize = config.cellSize,
@@ -543,10 +619,13 @@ fun LazyListScope.renderListByMode(
when (displayMode) { when (displayMode) {
DisplayMode.AGGREGATE -> { DisplayMode.AGGREGATE -> {
state.groupedRecords.forEach { (date, details) -> state.groupedRecords.forEach { (date, details) ->
stickyHeader(key = "header_$date") { item(key = "header_$date") {
DateHeader(date, details.sumOf { it.readTime }) DateHeader(date, details.sumOf { it.readTime })
} }
items(items = details, key = { "${it.bookName}_${it.bookAuthor}_${it.date}" }) { detail -> items(
items = details,
key = { "agg_item_${date}|${it.bookName}_${it.bookAuthor}_${it.date}" }
) { detail ->
SwipeActionContainer( SwipeActionContainer(
modifier = Modifier.animateItem(), modifier = Modifier.animateItem(),
startAction = SwipeAction( startAction = SwipeAction(
@@ -568,8 +647,8 @@ fun LazyListScope.renderListByMode(
DisplayMode.TIMELINE -> { DisplayMode.TIMELINE -> {
state.timelineRecords.forEach { (date, sessions) -> state.timelineRecords.forEach { (date, sessions) ->
stickyHeader(key = "timeline_header_$date") { DateHeader(date) } item(key = "timeline_header_$date") { DateHeader(date) }
items(items = sessions, key = { it.id }) { session -> items(items = sessions, key = { "timeline_item_${date}|${it.id}" }) { session ->
SwipeActionContainer( SwipeActionContainer(
modifier = Modifier.animateItem(), modifier = Modifier.animateItem(),
startAction = SwipeAction( startAction = SwipeAction(
@@ -637,7 +716,7 @@ fun LatestReadItem(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 8.dp), .adaptiveHorizontalPadding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Cover(coverPath) Cover(coverPath)
@@ -798,7 +877,7 @@ fun ReadRecordItem(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 8.dp), .adaptiveHorizontalPadding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Cover(coverPath) Cover(coverPath)
@@ -877,7 +956,7 @@ fun ReadingSummaryCard(
onClick = onClick, onClick = onClick,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp), .adaptiveHorizontalPadding(vertical = 8.dp),
containerColor = LegadoTheme.colorScheme.surfaceContainer containerColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
Row( Row(
@@ -345,7 +345,7 @@ fun SearchHistoryList(
trailingContent = { trailingContent = {
SmallIconButton( SmallIconButton(
onClick = { onDeleteHistory(item) }, onClick = { onDeleteHistory(item) },
icon = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = "删除" contentDescription = "删除"
) )
}, },
@@ -366,7 +366,7 @@ fun SearchHistoryList(
MediumOutlinedButton( MediumOutlinedButton(
onClick = onClearHistory, onClick = onClearHistory,
modifier = Modifier.fillMaxWidth(0.6f), modifier = Modifier.fillMaxWidth(0.6f),
icon = Icons.Outlined.DeleteSweep, imageVector = Icons.Outlined.DeleteSweep,
text = "清除搜索历史" text = "清除搜索历史"
) )
} }
@@ -469,7 +469,7 @@ fun TocScreen(
SmallOutlinedIconToggleButton( SmallOutlinedIconToggleButton(
checked = showVolumeMenu, checked = showVolumeMenu,
onCheckedChange = { showVolumeMenu = it }, onCheckedChange = { showVolumeMenu = it },
icon = Icons.AutoMirrored.Filled.FormatListBulleted, imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = "卷管理" contentDescription = "卷管理"
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -204,7 +204,7 @@ fun TxtRuleScreen(
} }
AppAlertDialog( AppAlertDialog(
data = showDeleteRuleDialog, // 传入 nullable rule 对象 data = showDeleteRuleDialog, // ä¼ å…¥ nullable çš?rule 对象
onDismissRequest = { showDeleteRuleDialog = null }, onDismissRequest = { showDeleteRuleDialog = null },
title = stringResource(R.string.delete), title = stringResource(R.string.delete),
confirmText = stringResource(R.string.ok), confirmText = stringResource(R.string.ok),
@@ -228,7 +228,7 @@ fun TxtRuleScreen(
editingRule = null editingRule = null
}, },
onSave = { updatedRule -> onSave = { updatedRule ->
//TODO:我很想把他改为自增主键,但为了兼容性日后再说 //TODO:我很想把他改为自增主键,但为了兼容性日åŽå†è¯?
if (editingRule == null) { if (editingRule == null) {
viewModel.insert(updatedRule) viewModel.insert(updatedRule)
} else { } else {
@@ -346,7 +346,7 @@ fun TxtRuleScreen(
trailingAction = { trailingAction = {
SmallIconButton( SmallIconButton(
onClick = { showDeleteRuleDialog = item.rule }, onClick = { showDeleteRuleDialog = item.rule },
icon = Icons.Default.Delete imageVector = Icons.Default.Delete
) )
} }
) )
@@ -368,3 +368,4 @@ fun TxtRuleScreen(
} }
} }
} }
@@ -68,7 +68,7 @@ fun DirectLinkUploadBottomSheet(
endAction = { endAction = {
MediumIconButton( MediumIconButton(
onClick = { showMenu = true }, onClick = { showMenu = true },
icon = Icons.Default.MoreVert imageVector = Icons.Default.MoreVert
) )
RoundDropdownMenu( RoundDropdownMenu(
expanded = showMenu, expanded = showMenu,
@@ -320,7 +320,7 @@ fun DictRuleScreen(
trailingAction = { trailingAction = {
SmallIconButton( SmallIconButton(
onClick = { showDeleteRuleDialog = item.rule }, onClick = { showDeleteRuleDialog = item.rule },
icon = Icons.Default.Delete imageVector = Icons.Default.Delete
) )
} }
) )
@@ -37,6 +37,8 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.service.WebService import io.legado.app.service.WebService
import io.legado.app.ui.about.CrashLogsDialog import io.legado.app.ui.about.CrashLogsDialog
import io.legado.app.ui.about.UpdateDialog import io.legado.app.ui.about.UpdateDialog
import io.legado.app.ui.book.import.local.ImportBookScreen
import io.legado.app.ui.book.import.remote.RemoteBookScreen
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.config.ConfigNavScreen import io.legado.app.ui.config.ConfigNavScreen
import io.legado.app.ui.config.ConfigTag import io.legado.app.ui.config.ConfigTag
@@ -74,6 +76,8 @@ open class MainActivity : BaseComposeActivity() {
private const val ROUTE_SETTINGS_COVER = "settings/cover" private const val ROUTE_SETTINGS_COVER = "settings/cover"
private const val ROUTE_SETTINGS_THEME = "settings/theme" private const val ROUTE_SETTINGS_THEME = "settings/theme"
private const val ROUTE_SETTINGS_BACKUP = "settings/backup" private const val ROUTE_SETTINGS_BACKUP = "settings/backup"
private const val ROUTE_IMPORT_LOCAL = "import/local"
private const val ROUTE_IMPORT_REMOTE = "import/remote"
fun createIntent(context: Context, configTag: String? = null): Intent { fun createIntent(context: Context, configTag: String? = null): Intent {
return Intent(context, MainActivity::class.java).apply { return Intent(context, MainActivity::class.java).apply {
@@ -120,6 +124,12 @@ open class MainActivity : BaseComposeActivity() {
@Serializable @Serializable
private data object MainRouteSettingsBackup : MainRoute private data object MainRouteSettingsBackup : MainRoute
@Serializable
private data object MainRouteImportLocal : MainRoute
@Serializable
private data object MainRouteImportRemote : MainRoute
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen() installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -260,6 +270,12 @@ open class MainActivity : BaseComposeActivity() {
useRail = useRail, useRail = useRail,
onOpenSettings = { onOpenSettings = {
navigateToRoute(backStack, MainRouteSettings) navigateToRoute(backStack, MainRouteSettings)
},
onNavigateToRemoteImport = {
navigateToRoute(backStack, MainRouteImportRemote)
},
onNavigateToLocalImport = {
navigateToRoute(backStack, MainRouteImportLocal)
} }
) )
} }
@@ -294,6 +310,18 @@ open class MainActivity : BaseComposeActivity() {
entry<MainRouteSettingsBackup> { entry<MainRouteSettingsBackup> {
BackupConfigScreen(onBackClick = { navigateBack(backStack) }) BackupConfigScreen(onBackClick = { navigateBack(backStack) })
} }
entry<MainRouteImportLocal> {
ImportBookScreen(
onBackClick = { navigateBack(backStack) }
)
}
entry<MainRouteImportRemote> {
RemoteBookScreen(
onBackClick = { navigateBack(backStack) }
)
}
} }
) )
} }
@@ -328,6 +356,17 @@ open class MainActivity : BaseComposeActivity() {
backStack.add(MainRouteSettings) backStack.add(MainRouteSettings)
backStack.add(route) backStack.add(route)
} }
MainRouteImportLocal,
MainRouteImportRemote -> {
if (currentRoute == MainRouteHome) {
backStack.add(route)
} else {
backStack.clear()
backStack.add(MainRouteHome)
backStack.add(route)
}
}
} }
} }
@@ -462,6 +501,8 @@ open class MainActivity : BaseComposeActivity() {
"settings/cover" -> MainRouteSettingsCover "settings/cover" -> MainRouteSettingsCover
"settings/theme" -> MainRouteSettingsTheme "settings/theme" -> MainRouteSettingsTheme
"settings/backup" -> MainRouteSettingsBackup "settings/backup" -> MainRouteSettingsBackup
"import/local" -> MainRouteImportLocal
"import/remote" -> MainRouteImportRemote
else -> MainRouteHome else -> MainRouteHome
} }
} }
@@ -89,7 +89,9 @@ import org.koin.androidx.compose.koinViewModel
fun MainScreen( fun MainScreen(
viewModel: MainViewModel = koinViewModel(), viewModel: MainViewModel = koinViewModel(),
useRail: Boolean, useRail: Boolean,
onOpenSettings: () -> Unit onOpenSettings: () -> Unit,
onNavigateToRemoteImport: () -> Unit,
onNavigateToLocalImport: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -373,7 +375,9 @@ fun MainScreen(
putExtra("author", book.author) putExtra("author", book.author)
putExtra("bookUrl", book.bookUrl) putExtra("bookUrl", book.bookUrl)
} }
} },
onNavigateToRemoteImport = onNavigateToRemoteImport,
onNavigateToLocalImport = onNavigateToLocalImport
) )
MainDestination.Explore -> ExploreScreen() MainDestination.Explore -> ExploreScreen()
@@ -67,12 +67,11 @@ import io.legado.app.R
import io.legado.app.base.BaseRuleEvent import io.legado.app.base.BaseRuleEvent
import io.legado.app.ui.about.AppLogSheet import io.legado.app.ui.about.AppLogSheet
import io.legado.app.ui.book.cache.CacheActivity import io.legado.app.ui.book.cache.CacheActivity
import io.legado.app.ui.book.import.local.ImportBookActivity
import io.legado.app.ui.book.import.remote.RemoteBookActivity
import io.legado.app.ui.book.manage.BookshelfManageActivity import io.legado.app.ui.book.manage.BookshelfManageActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.adaptiveContentPadding import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
@@ -100,7 +99,9 @@ import org.koin.androidx.compose.koinViewModel
fun BookshelfScreen( fun BookshelfScreen(
viewModel: BookshelfViewModel = koinViewModel(), viewModel: BookshelfViewModel = koinViewModel(),
onBookClick: (BookShelfItem) -> Unit, onBookClick: (BookShelfItem) -> Unit,
onBookLongClick: (BookShelfItem) -> Unit onBookLongClick: (BookShelfItem) -> Unit,
onNavigateToRemoteImport: () -> Unit,
onNavigateToLocalImport: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
@@ -196,7 +197,7 @@ fun BookshelfScreen(
} }
val bookGroupStyle = BookshelfConfig.bookGroupStyle val bookGroupStyle = BookshelfConfig.bookGroupStyle
// 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视图 // 控制是å¦å¤„äºŽâ€œæ–‡ä»¶å¤¹åˆ—è¡¨â€æ ¹è§†å›¾ï¼Œè¿˜æ˜¯â€œæ–‡ä»¶å¤¹å†…部â€ä¹¦ç±è§†å›?
var isInFolderRoot by remember(bookGroupStyle) { mutableStateOf(bookGroupStyle == 2) } var isInFolderRoot by remember(bookGroupStyle) { mutableStateOf(bookGroupStyle == 2) }
val baseTitle = when { val baseTitle = when {
bookGroupStyle == 1 -> { bookGroupStyle == 1 -> {
@@ -232,6 +233,10 @@ fun BookshelfScreen(
if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait
val bookshelfLayoutList = val bookshelfLayoutList =
if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape else BookshelfConfig.bookshelfLayoutListPortrait if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape else BookshelfConfig.bookshelfLayoutListPortrait
val totalHorizontalPadding =
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) 12.dp else 16.dp
val gridContentHorizontalPadding = totalHorizontalPadding / 2
val gridInnerHorizontalPadding = totalHorizontalPadding / 2
ListScaffold( ListScaffold(
title = title, title = title,
@@ -242,12 +247,12 @@ fun BookshelfScreen(
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = stringResource(R.string.add_remote_book), text = stringResource(R.string.add_remote_book),
onClick = { context.startActivity<RemoteBookActivity>(); dismiss() }, onClick = { onNavigateToRemoteImport(); dismiss() },
leadingIcon = { Icon(Icons.Default.Wifi, null) } leadingIcon = { Icon(Icons.Default.Wifi, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = stringResource(R.string.book_local), text = stringResource(R.string.book_local),
onClick = { context.startActivity<ImportBookActivity>(); dismiss() }, onClick = { onNavigateToLocalImport(); dismiss() },
leadingIcon = { Icon(Icons.Default.Save, null) } leadingIcon = { Icon(Icons.Default.Save, null) }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
@@ -345,7 +350,7 @@ fun BookshelfScreen(
SmallOutlinedIconToggleButton( SmallOutlinedIconToggleButton(
checked = showGroupMenu, checked = showGroupMenu,
onCheckedChange = { showGroupMenu = it }, onCheckedChange = { showGroupMenu = it },
icon = Icons.AutoMirrored.Filled.FormatListBulleted, imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = stringResource(R.string.group_manage) contentDescription = stringResource(R.string.group_manage)
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -407,15 +412,19 @@ fun BookshelfScreen(
if (bookGroupStyle == 2 && isRoot) { if (bookGroupStyle == 2 && isRoot) {
val folderColumns = val folderColumns =
if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid
val isGridMode = bookshelfLayoutMode != 0
FastScrollLazyVerticalGrid( FastScrollLazyVerticalGrid(
columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)), columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)),
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(horizontal = if (isGridMode) gridInnerHorizontalPadding else 0.dp),
contentPadding = adaptiveContentPadding( contentPadding = adaptiveContentPadding(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = 120.dp bottom = 120.dp,
horizontal = if (isGridMode) gridContentHorizontalPadding else 0.dp
), ),
verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp), verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
showFastScroll = BookshelfConfig.showBookshelfFastScroller showFastScroll = BookshelfConfig.showBookshelfFastScroller
) { ) {
itemsIndexed( itemsIndexed(
@@ -592,16 +601,23 @@ fun BookshelfPage(
onBookLongClick: (BookShelfItem) -> Unit onBookLongClick: (BookShelfItem) -> Unit
) { ) {
val columns = if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid val columns = if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid
val isGridMode = bookshelfLayoutMode != 0
val totalHorizontalPadding =
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) 12.dp else 16.dp
val gridContentHorizontalPadding = totalHorizontalPadding / 2
val gridInnerHorizontalPadding = totalHorizontalPadding / 2
FastScrollLazyVerticalGrid( FastScrollLazyVerticalGrid(
columns = GridCells.Fixed(columns.coerceAtLeast(1)), columns = GridCells.Fixed(columns.coerceAtLeast(1)),
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(horizontal = if (isGridMode) gridInnerHorizontalPadding else 0.dp),
contentPadding = adaptiveContentPadding( contentPadding = adaptiveContentPadding(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = 120.dp, bottom = 120.dp,
horizontal = if (bookshelfLayoutMode != 0) 12.dp else 0.dp horizontal = if (isGridMode) gridContentHorizontalPadding else 0.dp
), ),
verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp), verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
showFastScroll = BookshelfConfig.showBookshelfFastScroller showFastScroll = BookshelfConfig.showBookshelfFastScroller
) { ) {
items(books, key = { it.bookUrl }) { book -> items(books, key = { it.bookUrl }) { book ->
@@ -621,3 +637,4 @@ fun BookshelfPage(
} }
} }
} }
@@ -83,7 +83,7 @@ fun GroupManageSheet(
editingGroup = null editingGroup = null
isEditing = true isEditing = true
}, },
icon = Icons.Default.Add imageVector = Icons.Default.Add
) )
} }
} }
@@ -130,3 +130,4 @@ fun GroupManageSheet(
} }
} }
} }
@@ -249,8 +249,11 @@ fun ExploreScreen(
TextCard( TextCard(
text = item.bookSourceName, text = item.bookSourceName,
textStyle = LegadoTheme.typography.labelLarge, textStyle = LegadoTheme.typography.labelLarge,
backgroundColor = LegadoTheme.colorScheme.cardContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer,
cornerRadius = 8.dp,
horizontalPadding = 8.dp, horizontalPadding = 8.dp,
verticalPadding = 6.dp, verticalPadding = 8.dp,
onClick = { onClick = {
scope.launch { scope.launch {
val index = val index =
@@ -235,7 +235,7 @@ fun WebServiceSettingBlock(
) { ) {
SmallTextButton( SmallTextButton(
text = "复制地址", text = "复制地址",
icon = Icons.Default.ContentCopy, imageVector = Icons.Default.ContentCopy,
onClick = { onClick = {
onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress)) onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress))
} }
@@ -245,7 +245,7 @@ fun WebServiceSettingBlock(
SmallTextButton( SmallTextButton(
text = "浏览器打开", text = "浏览器打开",
icon = Icons.Default.OpenInBrowser, imageVector = Icons.Default.OpenInBrowser,
onClick = { onClick = {
onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress)) onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress))
} }
@@ -254,3 +254,4 @@ fun WebServiceSettingBlock(
} }
} }
} }
@@ -143,7 +143,9 @@ class ReplaceRuleActivity : BaseComposeActivity() {
} }
entry<ReplaceEditRoute> { route -> entry<ReplaceEditRoute> { route ->
val viewModel: ReplaceEditViewModel = koinViewModel { parametersOf(route) } val viewModel: ReplaceEditViewModel = koinViewModel(
key = "replace_edit_${System.identityHashCode(route)}"
) { parametersOf(route) }
ReplaceEditScreen( ReplaceEditScreen(
viewModel = viewModel, viewModel = viewModel,
@@ -42,13 +42,12 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import io.legado.app.ui.widget.components.AppFloatingActionButton import io.legado.app.ui.widget.components.AppFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.animateFloatingActionButton import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -73,8 +72,11 @@ import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.AlertButton import io.legado.app.ui.widget.components.button.AlertButton
import io.legado.app.ui.widget.components.button.ToggleChip
import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.button.TopBarNavigationButton import io.legado.app.ui.widget.components.button.TopBarNavigationButton
import io.legado.app.ui.widget.components.checkBox.CheckboxItem import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -115,15 +117,19 @@ fun ReplaceEditScreen(
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut() exit = fadeOut()
) { ) {
IconButton(onClick = { TopBarActionButton(
onClick = {
viewModel.save(onSaveSuccess) viewModel.save(onSaveSuccess)
}) { },
Icon(Icons.Default.Save, contentDescription = "保存") imageVector = Icons.Default.Save,
} contentDescription = "保存"
} )
IconButton(onClick = { showMenu = true }) {
Icon(Icons.Default.MoreVert, contentDescription = "更多操作")
} }
TopBarActionButton(
onClick = { showMenu = true },
imageVector = Icons.Default.MoreVert,
contentDescription = "更多操作"
)
RoundDropdownMenu( RoundDropdownMenu(
expanded = showMenu, expanded = showMenu,
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
@@ -158,7 +164,7 @@ fun ReplaceEditScreen(
onClick = { viewModel.save(onSaveSuccess) }, onClick = { viewModel.save(onSaveSuccess) },
tooltipText = "添加" tooltipText = "添加"
) { ) {
Icon(Icons.Default.Save, contentDescription = "保存") AppIcon(Icons.Default.Save, contentDescription = "保存")
} }
}, contentWindowInsets = WindowInsets(0, 0, 0, 0) }, contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding -> ) { innerPadding ->
@@ -242,53 +248,29 @@ fun ReplaceEditScreen(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start horizontalArrangement = Arrangement.Start
) { ) {
FilterChip( ToggleChip(
label = "标题",
selected = state.scopeTitle, selected = state.scopeTitle,
onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) }, checkedContentDescription = "已选择",
label = { AppText("标题") }, onToggle = { viewModel.onScopeTitleChange(!state.scopeTitle) }
leadingIcon = if (state.scopeTitle) {
{
Icon(
Icons.Default.Check,
contentDescription = "已选择",
Modifier.size(FilterChipDefaults.IconSize)
)
}
} else null
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
FilterChip( ToggleChip(
label = "内容",
selected = state.scopeContent, selected = state.scopeContent,
onClick = { viewModel.onScopeContentChange(!state.scopeContent) }, checkedContentDescription = "已选择",
label = { AppText("内容") }, onToggle = { viewModel.onScopeContentChange(!state.scopeContent) }
leadingIcon = if (state.scopeContent) {
{
Icon(
Icons.Default.Check,
contentDescription = "已选择",
Modifier.size(FilterChipDefaults.IconSize)
)
}
} else null
) )
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
FilterChip( ToggleChip(
label = "使用正则",
selected = state.isRegex, selected = state.isRegex,
onClick = { viewModel.onRegexChange(!state.isRegex) }, checkedContentDescription = "正则已启用",
label = { AppText("使用正则") }, onToggle = { viewModel.onRegexChange(!state.isRegex) }
leadingIcon = if (state.isRegex) {
{
Icon(
Icons.Default.Check,
contentDescription = "正则已启用",
Modifier.size(FilterChipDefaults.IconSize)
)
}
} else null
) )
} }
@@ -203,7 +203,7 @@ fun RssFavoritesScreen(
SelectionItemCard( SelectionItemCard(
title = rssStar.title, title = rssStar.title,
subtitle = if (rssStar.group.isNotBlank()) { subtitle = if (rssStar.group.isNotBlank()) {
"${rssStar.group} ${rssStar.pubDate ?: ""}" "${rssStar.group} �${rssStar.pubDate ?: ""}"
} else { } else {
rssStar.pubDate rssStar.pubDate
}, },
@@ -232,7 +232,7 @@ fun RssFavoritesScreen(
} }
SmallIconButton( SmallIconButton(
onClick = openAction, onClick = openAction,
icon = Icons.AutoMirrored.Filled.OpenInNew, imageVector = Icons.AutoMirrored.Filled.OpenInNew,
contentDescription = "Open" contentDescription = "Open"
) )
}, },
@@ -258,3 +258,4 @@ fun RssFavoritesScreen(
} }
} }
} }
@@ -365,7 +365,7 @@ fun RssSourceScreen(
trailingAction = { trailingAction = {
SmallIconButton( SmallIconButton(
onClick = { showDeleteRuleDialog = item.source }, onClick = { showDeleteRuleDialog = item.source },
icon = Icons.Default.Delete imageVector = Icons.Default.Delete
) )
} }
) )
@@ -387,3 +387,4 @@ fun RssSourceScreen(
} }
} }
} }
@@ -63,7 +63,7 @@ fun adaptiveContentPadding(
bottom: Dp, bottom: Dp,
horizontal: Dp horizontal: Dp
): PaddingValues { ): PaddingValues {
val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top else top + 4.dp val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 6.dp else top + 4.dp
return PaddingValues( return PaddingValues(
top = adjustedTop, top = adjustedTop,
bottom = bottom, bottom = bottom,
@@ -20,6 +20,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -37,7 +38,7 @@ fun CollapsibleHeader(
GlassCard( GlassCard(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 16.dp, top = 4.dp, end = 16.dp), .adaptiveHorizontalPadding(vertical = 4.dp),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
containerColor = LegadoTheme.colorScheme.surfaceContainer, containerColor = LegadoTheme.colorScheme.surfaceContainer,
onClick = onToggle onClick = onToggle
@@ -0,0 +1,35 @@
package io.legado.app.ui.widget.components
import androidx.compose.material3.Checkbox
import androidx.compose.material3.RadioButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.state.ToggleableState
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Checkbox
import top.yukonga.miuix.kmp.basic.RadioButton as MiuixRadioButton
@Composable
fun AppRadioButton(
selected: Boolean,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixRadioButton(
selected = selected,
onClick = onClick,
modifier = modifier,
enabled = enabled
)
} else {
RadioButton(
selected = selected,
onClick = onClick,
modifier = modifier,
enabled = enabled
)
}
}
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -120,7 +120,7 @@ private fun GroupItem(
) { ) {
SmallTextButton( SmallTextButton(
text = stringResource(id = R.string.ok), text = stringResource(id = R.string.ok),
icon = Icons.Default.Check, imageVector = Icons.Default.Check,
onClick = { onClick = {
onUpdateGroup(group, state.text.toString()) onUpdateGroup(group, state.text.toString())
expanded = false expanded = false
@@ -130,3 +130,4 @@ private fun GroupItem(
} }
) )
} }
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -31,7 +31,7 @@ fun ValueStepper(
val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange) val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange)
onValueChange(newValue) onValueChange(newValue)
}, },
icon = Icons.Default.Remove imageVector = Icons.Default.Remove
) )
TextCard( TextCard(
cornerRadius = 8.dp, cornerRadius = 8.dp,
@@ -46,7 +46,7 @@ fun ValueStepper(
val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange) val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange)
onValueChange(newValue) onValueChange(newValue)
}, },
icon = Icons.Default.Add imageVector = Icons.Default.Add
) )
} }
} }
@@ -41,7 +41,7 @@ import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
fun MediumIconButton( fun MediumIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
@@ -51,7 +51,7 @@ fun MediumIconButton(
modifier = modifier modifier = modifier
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -61,7 +61,7 @@ fun MediumIconButton(
modifier = modifier modifier = modifier
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -71,7 +71,7 @@ fun MediumIconButton(
@Composable @Composable
fun MediumOutlinedIconButton( fun MediumOutlinedIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
@@ -83,7 +83,7 @@ fun MediumOutlinedIconButton(
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -93,7 +93,7 @@ fun MediumOutlinedIconButton(
border = ButtonDefaults.outlinedButtonBorder() border = ButtonDefaults.outlinedButtonBorder()
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -103,7 +103,7 @@ fun MediumOutlinedIconButton(
@Composable @Composable
fun MediumOutlinedButton( fun MediumOutlinedButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
@@ -120,7 +120,7 @@ fun MediumOutlinedButton(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
MiuixText(text = text) MiuixText(text = text)
@@ -136,7 +136,7 @@ fun MediumOutlinedButton(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
Text(text = text) Text(text = text)
@@ -148,7 +148,7 @@ fun MediumOutlinedButton(
@Composable @Composable
fun MediumTonalIconButton( fun MediumTonalIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
@@ -161,7 +161,7 @@ fun MediumTonalIconButton(
backgroundColor = LegadoTheme.colorScheme.surfaceContainer backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -172,7 +172,7 @@ fun MediumTonalIconButton(
colors = IconButtonDefaults.filledTonalIconButtonColors() colors = IconButtonDefaults.filledTonalIconButtonColors()
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -183,7 +183,7 @@ fun MediumTonalIconButton(
fun MediumOutlinedIconToggleButton( fun MediumOutlinedIconToggleButton(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit, onCheckedChange: (Boolean) -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
@@ -208,7 +208,7 @@ fun MediumOutlinedIconToggleButton(
backgroundColor = containerColor backgroundColor = containerColor
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
tint = iconTint tint = iconTint
) )
@@ -220,7 +220,7 @@ fun MediumOutlinedIconToggleButton(
modifier = modifier modifier = modifier
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -64,7 +64,7 @@ private fun SmallNoMinTouchTarget(content: @Composable () -> Unit) {
@Composable @Composable
fun SmallIconButton( fun SmallIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
contentDescription: String? = null contentDescription: String? = null
) { ) {
if (ThemeResolver.isMiuixEngine(composeEngine)) { if (ThemeResolver.isMiuixEngine(composeEngine)) {
@@ -72,7 +72,7 @@ fun SmallIconButton(
onClick = onClick onClick = onClick
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription contentDescription = contentDescription
) )
} }
@@ -84,7 +84,7 @@ fun SmallIconButton(
shape = IconButtonDefaults.extraSmallRoundShape, shape = IconButtonDefaults.extraSmallRoundShape,
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize), modifier = Modifier.size(smallIconSize),
) )
@@ -97,11 +97,10 @@ fun SmallIconButton(
@Composable @Composable
fun SmallOutlinedIconButton( fun SmallOutlinedIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
val isMiuix = ThemeResolver.isMiuixEngine(composeEngine) val isMiuix = ThemeResolver.isMiuixEngine(composeEngine)
if (isMiuix) { if (isMiuix) {
MiuixIconButton( MiuixIconButton(
@@ -110,7 +109,7 @@ fun SmallOutlinedIconButton(
backgroundColor = LegadoTheme.colorScheme.surfaceContainer backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize) modifier = Modifier.size(SmallMiuixIconSize)
) )
@@ -124,7 +123,7 @@ fun SmallOutlinedIconButton(
border = ButtonDefaults.outlinedButtonBorder() border = ButtonDefaults.outlinedButtonBorder()
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize) modifier = Modifier.size(smallIconSize)
) )
@@ -137,7 +136,7 @@ fun SmallOutlinedIconButton(
@Composable @Composable
fun SmallTonalIconButton( fun SmallTonalIconButton(
onClick: () -> Unit, onClick: () -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
@@ -150,7 +149,7 @@ fun SmallTonalIconButton(
backgroundColor = LegadoTheme.colorScheme.surfaceContainer backgroundColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size(SmallMiuixIconSize) modifier = Modifier.size(SmallMiuixIconSize)
) )
@@ -164,7 +163,7 @@ fun SmallTonalIconButton(
colors = IconButtonDefaults.filledTonalIconButtonColors() colors = IconButtonDefaults.filledTonalIconButtonColors()
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize) modifier = Modifier.size(smallIconSize)
) )
@@ -178,7 +177,7 @@ fun SmallTonalIconButton(
fun SmallOutlinedIconToggleButton( fun SmallOutlinedIconToggleButton(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit, onCheckedChange: (Boolean) -> Unit,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentDescription: String? = null contentDescription: String? = null
) { ) {
@@ -205,7 +204,7 @@ fun SmallOutlinedIconToggleButton(
backgroundColor = containerColor backgroundColor = containerColor
) { ) {
MiuixIcon( MiuixIcon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
tint = iconTint, tint = iconTint,
modifier = Modifier.size(SmallMiuixIconSize) modifier = Modifier.size(SmallMiuixIconSize)
@@ -233,7 +232,7 @@ fun SmallOutlinedIconToggleButton(
shapes = toggleShapes shapes = toggleShapes
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.size(smallIconSize), modifier = Modifier.size(smallIconSize),
) )
@@ -22,7 +22,7 @@ import top.yukonga.miuix.kmp.basic.Text as MiuixText
@Composable @Composable
fun SmallTextButton( fun SmallTextButton(
text: String, text: String,
icon: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: () -> Unit onClick: () -> Unit
) { ) {
@@ -44,7 +44,7 @@ fun SmallTextButton(
shape = MaterialTheme.shapes.small shape = MaterialTheme.shapes.small
) { ) {
Icon( Icon(
imageVector = icon, imageVector = imageVector,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
@@ -0,0 +1,87 @@
package io.legado.app.ui.widget.components.button
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
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.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
import top.yukonga.miuix.kmp.basic.Text as MiuixText
import top.yukonga.miuix.kmp.theme.MiuixTheme
@Composable
fun ToggleChip(
label: String,
selected: Boolean,
onToggle: () -> Unit,
modifier: Modifier = Modifier,
checkedContentDescription: String = "已选择"
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixIconButton(
onClick = onToggle,
modifier = modifier,
backgroundColor = if (selected) {
MiuixTheme.colorScheme.primaryContainer
} else {
MiuixTheme.colorScheme.surfaceContainer
}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp)
) {
AnimatedVisibility(visible = selected) {
Row(verticalAlignment = Alignment.CenterVertically) {
MiuixIcon(
imageVector = Icons.Default.Check,
contentDescription = checkedContentDescription,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(6.dp))
}
}
MiuixText(
text = label,
style = LegadoTheme.typography.labelSmall,
maxLines = 1,
softWrap = false
)
}
}
} else {
FilterChip(
selected = selected,
onClick = onToggle,
modifier = modifier,
label = { Text(label) },
leadingIcon = if (selected) {
{
Icon(
Icons.Default.Check,
contentDescription = checkedContentDescription,
Modifier.size(FilterChipDefaults.IconSize)
)
}
} else null
)
}
}
@@ -181,7 +181,7 @@ fun SelectionItemCard(
if (onClickEdit != null) { if (onClickEdit != null) {
SmallIconButton( SmallIconButton(
onClick = onClickEdit, onClick = onClickEdit,
icon = Icons.Default.Edit, imageVector = Icons.Default.Edit,
contentDescription = "Edit" contentDescription = "Edit"
) )
} }
@@ -194,7 +194,7 @@ fun SelectionItemCard(
Box { Box {
SmallIconButton( SmallIconButton(
onClick = { showMenu = true }, onClick = { showMenu = true },
icon = Icons.Default.MoreVert, imageVector = Icons.Default.MoreVert,
contentDescription = "More" contentDescription = "More"
) )
RoundDropdownMenu( RoundDropdownMenu(
@@ -272,3 +272,4 @@ fun LazyItemScope.ReorderableSelectionItem(
) )
} }
} }
@@ -19,7 +19,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccessTime import androidx.compose.material.icons.filled.AccessTime
import androidx.compose.material.icons.filled.FormatListNumbered import androidx.compose.material.icons.filled.FormatListNumbered
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -43,24 +42,18 @@ import io.legado.app.ui.widget.components.text.AppText
import java.time.LocalDate import java.time.LocalDate
/** /**
* 热力图日历顶部操作栏 * 热力图日历弹窗标题
*/
const val HEATMAP_CALENDAR_TITLE = "时间线"
/**
* 热力图日历弹窗左侧操作
*/ */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun HeatmapCalendarTopBar( fun HeatmapCalendarStartAction(
currentMode: HeatmapMode, currentMode: HeatmapMode,
onModeChanged: (HeatmapMode) -> Unit, onModeChanged: (HeatmapMode) -> Unit,
onClearDate: () -> Unit,
modifier: Modifier = Modifier
) { ) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
AppText("时间线", style = LegadoTheme.typography.titleMedium)
Row(verticalAlignment = Alignment.CenterVertically) {
MediumAnimatedActionButton( MediumAnimatedActionButton(
checked = currentMode == HeatmapMode.TIME, checked = currentMode == HeatmapMode.TIME,
onCheckedChange = { onCheckedChange = {
@@ -71,16 +64,20 @@ fun HeatmapCalendarTopBar(
activeText = "按时长", activeText = "按时长",
inactiveText = "按次数" inactiveText = "按次数"
) )
}
Spacer(modifier = Modifier.width(8.dp)) /**
* 热力图日历弹窗右侧操作
*/
@Composable
fun HeatmapCalendarEndAction(
onClearDate: () -> Unit
) {
MediumOutlinedIconButton( MediumOutlinedIconButton(
onClick = onClearDate, onClick = onClearDate,
icon = Icons.Outlined.Delete, imageVector = Icons.Outlined.Delete,
) )
} }
}
}
/** /**
* 星期标签列(周一到周日) * 星期标签列(周一到周日)
@@ -41,7 +41,6 @@ fun AppModalBottomSheet(
title: String? = null, title: String? = null,
startAction: @Composable (() -> Unit)? = null, startAction: @Composable (() -> Unit)? = null,
endAction: @Composable (() -> Unit)? = null, endAction: @Composable (() -> Unit)? = null,
containerColor: Color = LegadoTheme.colorScheme.surfaceContainer,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) { if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
@@ -52,14 +51,15 @@ fun AppModalBottomSheet(
startAction = startAction, startAction = startAction,
endAction = endAction, endAction = endAction,
insideMargin = DpSize(16.dp, 12.dp), insideMargin = DpSize(16.dp, 12.dp),
backgroundColor = containerColor,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onDismissFinished = onDismissRequest, onDismissFinished = onDismissRequest,
enableWindowDim = true, enableWindowDim = true,
allowDismiss = true allowDismiss = true
) { ) {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
content = content content = content
) )
} }
@@ -75,7 +75,6 @@ fun AppModalBottomSheet(
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
sheetState = sheetState, sheetState = sheetState,
containerColor = containerColor,
contentColor = colorScheme.onSurface, contentColor = colorScheme.onSurface,
dragHandle = { BottomSheetDefaults.DragHandle() } dragHandle = { BottomSheetDefaults.DragHandle() }
) { ) {
@@ -32,8 +32,7 @@ fun OptionSheet(
AppModalBottomSheet( AppModalBottomSheet(
show = show, show = show,
title = title, title = title,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest
containerColor = LegadoTheme.colorScheme.surface
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -34,15 +34,19 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppFloatingActionButton import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
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.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
* 通用编辑数据包装,用于适配不同的规则实体 * 通用编辑数据包装,用于适配不同的规则
*/ */
data class RuleEditFields( data class RuleEditFields(
val name: String = "", val name: String = "",
@@ -80,28 +84,30 @@ fun <T> RuleEditSheet(
AppModalBottomSheet( AppModalBottomSheet(
title = title, title = title,
startAction = { startAction = {
IconButton(onClick = onDismissRequest) { MediumIconButton(
Icon( onClick = onDismissRequest,
Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel) contentDescription = "Close",
) )
}
}, },
endAction = { endAction = {
IconButton(onClick = { showMenu = true }) { Box{
Icon(Icons.Default.MoreVert, contentDescription = "More") MediumIconButton(
} onClick = { showMenu = true },
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { imageVector = Icons.Default.MoreVert,
DropdownMenuItem( contentDescription = "More"
text = { AppText(stringResource(R.string.copy_rule)) }, )
RoundDropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
RoundDropdownMenuItem(
text = stringResource(R.string.copy_rule),
leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) }, leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) },
onClick = { onClick = {
onCopy(getCurrentEntity()) onCopy(getCurrentEntity())
showMenu = false showMenu = false
} }
) )
DropdownMenuItem( RoundDropdownMenuItem(
text = { AppText(stringResource(R.string.paste_rule)) }, text = stringResource(R.string.paste_rule),
leadingIcon = { Icon(Icons.Default.ContentPaste, null) }, leadingIcon = { Icon(Icons.Default.ContentPaste, null) },
onClick = { onClick = {
scope.launch { scope.launch {
@@ -116,6 +122,7 @@ fun <T> RuleEditSheet(
} }
) )
} }
}
}, },
show = show, show = show,
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
@@ -132,6 +139,7 @@ fun <T> RuleEditSheet(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
backgroundColor = LegadoTheme.colorScheme.surface,
label = stringResource(R.string.name), label = stringResource(R.string.name),
singleLine = true singleLine = true
) )
@@ -139,12 +147,14 @@ fun <T> RuleEditSheet(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = rule1, value = rule1,
onValueChange = { rule1 = it }, onValueChange = { rule1 = it },
backgroundColor = LegadoTheme.colorScheme.surface,
label = label1 label = label1
) )
AppTextField( AppTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = rule2, value = rule2,
onValueChange = { rule2 = it }, onValueChange = { rule2 = it },
backgroundColor = LegadoTheme.colorScheme.surface,
label = label2, label = label2,
minLines = 3 minLines = 3
) )
@@ -84,14 +84,14 @@ fun InputSettingItem(
) { ) {
SmallTextButton( SmallTextButton(
text = "默认", text = "默认",
icon = Icons.Default.Replay, imageVector = Icons.Default.Replay,
onClick = { onClick = {
state.edit { replace(0, length, defaultValue.toString()) } state.edit { replace(0, length, defaultValue.toString()) }
} }
) )
SmallTextButton( SmallTextButton(
text = "确认", text = "确认",
icon = Icons.Default.Check, imageVector = Icons.Default.Check,
onClick = { onClick = {
onConfirm(state.text.toString()) onConfirm(state.text.toString())
expanded = false expanded = false
@@ -133,14 +133,14 @@ fun InputSettingItem(
) { ) {
SmallTextButton( SmallTextButton(
text = "默认", text = "默认",
icon = Icons.Default.Replay, imageVector = Icons.Default.Replay,
onClick = { onClick = {
state.edit { replace(0, length, defaultValue.toString()) } state.edit { replace(0, length, defaultValue.toString()) }
} }
) )
SmallTextButton( SmallTextButton(
text = "确认", text = "确认",
icon = Icons.Default.Check, imageVector = Icons.Default.Check,
onClick = { onClick = {
onConfirm(state.text.toString()) onConfirm(state.text.toString())
expanded = false expanded = false
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components.settingItem package io.legado.app.ui.widget.components.settingItem
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@@ -78,7 +78,7 @@ fun SliderSettingItem(
MiuixTextField( MiuixTextField(
state = textFieldState, state = textFieldState,
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
label = "输入数值 (${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})", label = "杈撳叆鏁板€?(${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})",
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
inputTransformation = { inputTransformation = {
@@ -120,16 +120,16 @@ fun SliderSettingItem(
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
SmallTextButton( SmallTextButton(
text = if (isInputMode) "滑块" else "输入", text = if (isInputMode) "婊戝潡" else "杈撳叆",
icon = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit, imageVector = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit,
onClick = { isInputMode = !isInputMode } onClick = { isInputMode = !isInputMode }
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
SmallTextButton( SmallTextButton(
text = "默认", text = "榛樿",
icon = Icons.Default.RestartAlt, imageVector = Icons.Default.RestartAlt,
onClick = { onClick = {
onValueChange(defaultValue) onValueChange(defaultValue)
textFieldState.edit { textFieldState.edit {
@@ -163,7 +163,7 @@ fun SliderSettingItem(
TextField( TextField(
state = textFieldState, state = textFieldState,
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
label = { AppText("输入数值 (${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})") }, label = { AppText("杈撳叆鏁板€?(${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -212,16 +212,16 @@ fun SliderSettingItem(
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
SmallTextButton( SmallTextButton(
text = if (isInputMode) "滑块" else "输入", text = if (isInputMode) "婊戝潡" else "杈撳叆",
icon = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit, imageVector = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit,
onClick = { isInputMode = !isInputMode } onClick = { isInputMode = !isInputMode }
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
SmallTextButton( SmallTextButton(
text = "默认", text = "榛樿",
icon = Icons.Default.RestartAlt, imageVector = Icons.Default.RestartAlt,
onClick = { onClick = {
onValueChange(defaultValue) onValueChange(defaultValue)
textFieldState.edit { textFieldState.edit {
@@ -238,3 +238,4 @@ fun SliderSettingItem(
) )
} }
} }
@@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -128,7 +129,7 @@ private fun SwipeBackground(
targetValue = if (isThresholdReached) targetValue = if (isThresholdReached)
action.background action.background
else else
MaterialTheme.colorScheme.surfaceVariant, LegadoTheme.colorScheme.surfaceVariant,
label = "bgColor" label = "bgColor"
) )
@@ -160,7 +161,7 @@ private fun SwipeBackground(
tint = if (isThresholdReached) tint = if (isThresholdReached)
contentColorFor(action.background) contentColorFor(action.background)
else else
MaterialTheme.colorScheme.onSurfaceVariant LegadoTheme.colorScheme.onSurfaceVariant
) )
} }
} }
+5 -5
View File
@@ -1,9 +1,9 @@
[versions] [versions]
accompanistWebview = "0.36.0" accompanistWebview = "0.36.0"
adaptive = "1.3.0-alpha09" adaptive = "1.3.0-alpha10"
adaptiveLayout = "1.3.0-alpha09" adaptiveLayout = "1.3.0-alpha10"
adaptiveNavigation = "1.3.0-alpha09" adaptiveNavigation = "1.3.0-alpha10"
animation = "1.10.6" animation = "1.10.6"
biometric = "1.4.0-alpha06" biometric = "1.4.0-alpha06"
coilCompose = "2.7.0" coilCompose = "2.7.0"
@@ -46,8 +46,8 @@ liveeventbus = "1.8.14"
navigation3 = "1.0.1" navigation3 = "1.0.1"
markdownCompose = "0.5.0" markdownCompose = "0.5.0"
markwon = "4.6.2" markwon = "4.6.2"
material = "1.14.0-alpha10" material = "1.14.0-beta01"
material3 = "1.5.0-alpha15" material3 = "1.5.0-alpha17"
material3IconsExtended = "1.7.8" material3IconsExtended = "1.7.8"
materialKolor = "4.1.1" materialKolor = "4.1.1"
media = "1.7.1" media = "1.7.1"