diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 00ea91137..6d8a02238 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -274,16 +274,6 @@ android:configChanges="orientation|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTop" /> - - - - : - VMBaseActivity() { - - 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) { - 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 - } - }) - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt b/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt new file mode 100644 index 000000000..d76617c3d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt @@ -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, + 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 + ) + } + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt index 815f002a1..e53b377dd 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt @@ -145,15 +145,15 @@ fun BookInfoEditContent( ) ) }, - icon = Icons.Default.ImageSearch + imageVector = Icons.Default.ImageSearch ) MediumOutlinedIconButton( onClick = { selectCover.launch() }, - icon = Icons.Default.FolderOpen + imageVector = Icons.Default.FolderOpen ) MediumOutlinedIconButton( onClick = { viewModel.resetCover() }, - icon = Icons.Default.Replay + imageVector = Icons.Default.Replay ) } Spacer(modifier = Modifier.height(4.dp)) diff --git a/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt b/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt index 163045988..318e45ed5 100644 --- a/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/readRecord/ReadRecordScreen.kt @@ -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.ReadRecordDetail 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.widget.CollapsibleHeader 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.TopBarNavigationButton 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.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.HeatmapLegend 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.rememberWeeks 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.swipe.SwipeAction import io.legado.app.ui.widget.components.swipe.SwipeActionContainer @@ -118,6 +124,7 @@ fun ReadRecordScreen( val displayMode by viewModel.displayMode.collectAsState() var showSearch by remember { mutableStateOf(false) } var showCalendar by remember { mutableStateOf(false) } + var heatmapMode by remember { mutableStateOf(HeatmapMode.COUNT) } val listState = rememberLazyListState() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() @@ -131,6 +138,36 @@ fun ReadRecordScreen( 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) { if (state.searchKey.isNullOrBlank()) { @@ -198,41 +235,52 @@ fun ReadRecordScreen( } } ) { padding -> - Column(modifier = Modifier - .padding(padding) - .fillMaxSize()) { - val contentState = when { - state.isLoading -> "LOADING" - (displayMode == DisplayMode.AGGREGATE && state.groupedRecords.isEmpty()) || - (displayMode == DisplayMode.TIMELINE && state.timelineRecords.isEmpty()) || - (displayMode == DisplayMode.LATEST && state.latestRecords.isEmpty()) -> "EMPTY" - else -> "CONTENT" - } - AnimatedContent( - targetState = contentState, - label = "MainContentAnimation" - ) { targetState -> - when (targetState) { - "LOADING" -> { - EmptyMessageView( - modifier = Modifier.fillMaxSize(), - message = "加载中", - isLoading = true - ) - } + val contentState = when { + state.isLoading -> "LOADING" + (displayMode == DisplayMode.AGGREGATE && state.groupedRecords.isEmpty()) || + (displayMode == DisplayMode.TIMELINE && state.timelineRecords.isEmpty()) || + (displayMode == DisplayMode.LATEST && state.latestRecords.isEmpty()) -> "EMPTY" + else -> "CONTENT" + } + AnimatedContent( + targetState = contentState, + label = "MainContentAnimation" + ) { targetState -> + when (targetState) { + "LOADING" -> { + EmptyMessageView( + modifier = Modifier + .fillMaxSize() + .padding( + top = padding.calculateTopPadding(), + bottom = padding.calculateBottomPadding() + ), + message = "加载中", + isLoading = true + ) + } - "EMPTY" -> { - EmptyMessageView( - modifier = Modifier.fillMaxSize(), - message = "没有记录" - ) - } + "EMPTY" -> { + EmptyMessageView( + modifier = Modifier + .fillMaxSize() + .padding( + top = padding.calculateTopPadding(), + bottom = padding.calculateBottomPadding() + ), + message = "没有记录" + ) + } - "CONTENT" -> { + "CONTENT" -> { + Box(modifier = Modifier.fillMaxSize()) { LazyColumn( state = listState, - modifier = Modifier - .nestedScroll(scrollBehavior.nestedScrollConnection) + modifier = Modifier.fillMaxSize(), + contentPadding = adaptiveContentPaddingOnlyVertical( + top = padding.calculateTopPadding(), + bottom = padding.calculateBottomPadding() + 16.dp + ) ) { item(key = "summary_card") { 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( 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( dailyReadCounts = state.dailyReadCounts, dailyReadTimes = state.dailyReadTimes, + currentMode = heatmapMode, selectedDate = state.selectedDate, onDateSelected = { date -> viewModel.setSelectedDate(date) showCalendar = false - }, - onClearDate = { - viewModel.setSelectedDate(null) - showCalendar = false } ) } @@ -404,13 +490,11 @@ fun HeatmapCalendarSection( modifier: Modifier = Modifier, dailyReadCounts: Map, dailyReadTimes: Map, + currentMode: HeatmapMode, selectedDate: LocalDate?, onDateSelected: (LocalDate) -> Unit, - onClearDate: () -> Unit, config: HeatmapConfig = HeatmapConfig() ) { - var currentMode by remember { mutableStateOf(HeatmapMode.COUNT) } - val (startDate, endDate) = rememberDateRange(dailyReadCounts, dailyReadTimes) val days = rememberDaysInRange(startDate, endDate) val weeks = rememberWeeks(days, startDate) @@ -452,14 +536,6 @@ fun HeatmapCalendarSection( .padding(horizontal = 16.dp, vertical = 8.dp) .padding(bottom = 32.dp) ) { - HeatmapCalendarTopBar( - currentMode = currentMode, - onModeChanged = { currentMode = it }, - onClearDate = onClearDate - ) - - Spacer(modifier = Modifier.height(8.dp)) - Row(modifier = Modifier.fillMaxWidth()) { WeekdayLabelsColumn( cellSize = config.cellSize, @@ -543,10 +619,13 @@ fun LazyListScope.renderListByMode( when (displayMode) { DisplayMode.AGGREGATE -> { state.groupedRecords.forEach { (date, details) -> - stickyHeader(key = "header_$date") { + item(key = "header_$date") { 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( modifier = Modifier.animateItem(), startAction = SwipeAction( @@ -568,8 +647,8 @@ fun LazyListScope.renderListByMode( DisplayMode.TIMELINE -> { state.timelineRecords.forEach { (date, sessions) -> - stickyHeader(key = "timeline_header_$date") { DateHeader(date) } - items(items = sessions, key = { it.id }) { session -> + item(key = "timeline_header_$date") { DateHeader(date) } + items(items = sessions, key = { "timeline_item_${date}|${it.id}" }) { session -> SwipeActionContainer( modifier = Modifier.animateItem(), startAction = SwipeAction( @@ -637,7 +716,7 @@ fun LatestReadItem( modifier = modifier .fillMaxWidth() .clickable(onClick = onClick) - .padding(horizontal = 16.dp, vertical = 8.dp), + .adaptiveHorizontalPadding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Cover(coverPath) @@ -798,7 +877,7 @@ fun ReadRecordItem( modifier = modifier .fillMaxWidth() .clickable(onClick = onClick) - .padding(horizontal = 16.dp, vertical = 8.dp), + .adaptiveHorizontalPadding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Cover(coverPath) @@ -877,7 +956,7 @@ fun ReadingSummaryCard( onClick = onClick, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), + .adaptiveHorizontalPadding(vertical = 8.dp), containerColor = LegadoTheme.colorScheme.surfaceContainer ) { Row( diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt index bdb453b58..ac90bcc8d 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt @@ -345,7 +345,7 @@ fun SearchHistoryList( trailingContent = { SmallIconButton( onClick = { onDeleteHistory(item) }, - icon = Icons.Default.Close, + imageVector = Icons.Default.Close, contentDescription = "删除" ) }, @@ -366,7 +366,7 @@ fun SearchHistoryList( MediumOutlinedButton( onClick = onClearHistory, modifier = Modifier.fillMaxWidth(0.6f), - icon = Icons.Outlined.DeleteSweep, + imageVector = Icons.Outlined.DeleteSweep, text = "清除搜索历史" ) } diff --git a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt index 4321e0fee..b025d7903 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt @@ -469,7 +469,7 @@ fun TocScreen( SmallOutlinedIconToggleButton( checked = showVolumeMenu, onCheckedChange = { showVolumeMenu = it }, - icon = Icons.AutoMirrored.Filled.FormatListBulleted, + imageVector = Icons.AutoMirrored.Filled.FormatListBulleted, contentDescription = "卷管理" ) RoundDropdownMenu( diff --git a/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt b/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt index 14fbfd54b..09d87006d 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/rule/TxtRuleScreen.kt @@ -204,7 +204,7 @@ fun TxtRuleScreen( } AppAlertDialog( - data = showDeleteRuleDialog, // 传入 nullable 的 rule 对象 + data = showDeleteRuleDialog, // 传入 nullable ?rule 对象 onDismissRequest = { showDeleteRuleDialog = null }, title = stringResource(R.string.delete), confirmText = stringResource(R.string.ok), @@ -228,7 +228,7 @@ fun TxtRuleScreen( editingRule = null }, onSave = { updatedRule -> - //TODO:我很想把他改为自增主键,但为了兼容性日后再说 + //TODO:我很想把他改为自增主键,但为了兼容性日后再? if (editingRule == null) { viewModel.insert(updatedRule) } else { @@ -346,7 +346,7 @@ fun TxtRuleScreen( trailingAction = { SmallIconButton( onClick = { showDeleteRuleDialog = item.rule }, - icon = Icons.Default.Delete + imageVector = Icons.Default.Delete ) } ) @@ -368,3 +368,4 @@ fun TxtRuleScreen( } } } + diff --git a/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt b/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt index a0541c287..10f0ff114 100644 --- a/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt +++ b/app/src/main/java/io/legado/app/ui/config/otherConfig/DirectLinkUploadBottomSheet.kt @@ -68,7 +68,7 @@ fun DirectLinkUploadBottomSheet( endAction = { MediumIconButton( onClick = { showMenu = true }, - icon = Icons.Default.MoreVert + imageVector = Icons.Default.MoreVert ) RoundDropdownMenu( expanded = showMenu, diff --git a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt index 7dcff4ab9..79c276cd4 100644 --- a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt +++ b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleScreen.kt @@ -320,7 +320,7 @@ fun DictRuleScreen( trailingAction = { SmallIconButton( onClick = { showDeleteRuleDialog = item.rule }, - icon = Icons.Default.Delete + imageVector = Icons.Default.Delete ) } ) @@ -341,4 +341,4 @@ fun DictRuleScreen( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index e7778a926..857c559df 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -37,6 +37,8 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.service.WebService import io.legado.app.ui.about.CrashLogsDialog 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.config.ConfigNavScreen 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_THEME = "settings/theme" 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 { return Intent(context, MainActivity::class.java).apply { @@ -120,6 +124,12 @@ open class MainActivity : BaseComposeActivity() { @Serializable private data object MainRouteSettingsBackup : MainRoute + @Serializable + private data object MainRouteImportLocal : MainRoute + + @Serializable + private data object MainRouteImportRemote : MainRoute + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) @@ -260,6 +270,12 @@ open class MainActivity : BaseComposeActivity() { useRail = useRail, onOpenSettings = { navigateToRoute(backStack, MainRouteSettings) + }, + onNavigateToRemoteImport = { + navigateToRoute(backStack, MainRouteImportRemote) + }, + onNavigateToLocalImport = { + navigateToRoute(backStack, MainRouteImportLocal) } ) } @@ -294,6 +310,18 @@ open class MainActivity : BaseComposeActivity() { entry { BackupConfigScreen(onBackClick = { navigateBack(backStack) }) } + + entry { + ImportBookScreen( + onBackClick = { navigateBack(backStack) } + ) + } + + entry { + RemoteBookScreen( + onBackClick = { navigateBack(backStack) } + ) + } } ) } @@ -328,6 +356,17 @@ open class MainActivity : BaseComposeActivity() { backStack.add(MainRouteSettings) 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/theme" -> MainRouteSettingsTheme "settings/backup" -> MainRouteSettingsBackup + "import/local" -> MainRouteImportLocal + "import/remote" -> MainRouteImportRemote else -> MainRouteHome } } diff --git a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt index 07f77abc4..8b630f537 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt @@ -89,7 +89,9 @@ import org.koin.androidx.compose.koinViewModel fun MainScreen( viewModel: MainViewModel = koinViewModel(), useRail: Boolean, - onOpenSettings: () -> Unit + onOpenSettings: () -> Unit, + onNavigateToRemoteImport: () -> Unit, + onNavigateToLocalImport: () -> Unit ) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() @@ -373,7 +375,9 @@ fun MainScreen( putExtra("author", book.author) putExtra("bookUrl", book.bookUrl) } - } + }, + onNavigateToRemoteImport = onNavigateToRemoteImport, + onNavigateToLocalImport = onNavigateToLocalImport ) MainDestination.Explore -> ExploreScreen() diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index 2ba1dad70..5bb726c4e 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt @@ -67,12 +67,11 @@ import io.legado.app.R import io.legado.app.base.BaseRuleEvent import io.legado.app.ui.about.AppLogSheet 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.search.SearchActivity import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig 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.adaptiveHorizontalPadding import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton @@ -100,7 +99,9 @@ import org.koin.androidx.compose.koinViewModel fun BookshelfScreen( viewModel: BookshelfViewModel = koinViewModel(), onBookClick: (BookShelfItem) -> Unit, - onBookLongClick: (BookShelfItem) -> Unit + onBookLongClick: (BookShelfItem) -> Unit, + onNavigateToRemoteImport: () -> Unit, + onNavigateToLocalImport: () -> Unit ) { val context = LocalContext.current val uiState by viewModel.uiState.collectAsState() @@ -196,7 +197,7 @@ fun BookshelfScreen( } val bookGroupStyle = BookshelfConfig.bookGroupStyle - // 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视图 + // 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视? var isInFolderRoot by remember(bookGroupStyle) { mutableStateOf(bookGroupStyle == 2) } val baseTitle = when { bookGroupStyle == 1 -> { @@ -232,6 +233,10 @@ fun BookshelfScreen( if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait val bookshelfLayoutList = 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( title = title, @@ -242,12 +247,12 @@ fun BookshelfScreen( dropDownMenuContent = { dismiss -> RoundDropdownMenuItem( text = stringResource(R.string.add_remote_book), - onClick = { context.startActivity(); dismiss() }, + onClick = { onNavigateToRemoteImport(); dismiss() }, leadingIcon = { Icon(Icons.Default.Wifi, null) } ) RoundDropdownMenuItem( text = stringResource(R.string.book_local), - onClick = { context.startActivity(); dismiss() }, + onClick = { onNavigateToLocalImport(); dismiss() }, leadingIcon = { Icon(Icons.Default.Save, null) } ) RoundDropdownMenuItem( @@ -345,7 +350,7 @@ fun BookshelfScreen( SmallOutlinedIconToggleButton( checked = showGroupMenu, onCheckedChange = { showGroupMenu = it }, - icon = Icons.AutoMirrored.Filled.FormatListBulleted, + imageVector = Icons.AutoMirrored.Filled.FormatListBulleted, contentDescription = stringResource(R.string.group_manage) ) RoundDropdownMenu( @@ -407,15 +412,19 @@ fun BookshelfScreen( if (bookGroupStyle == 2 && isRoot) { val folderColumns = if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid + val isGridMode = bookshelfLayoutMode != 0 FastScrollLazyVerticalGrid( columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)), - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = if (isGridMode) gridInnerHorizontalPadding else 0.dp), contentPadding = adaptiveContentPadding( 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), - horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp), + verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), + horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), showFastScroll = BookshelfConfig.showBookshelfFastScroller ) { itemsIndexed( @@ -592,16 +601,23 @@ fun BookshelfPage( onBookLongClick: (BookShelfItem) -> Unit ) { 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( columns = GridCells.Fixed(columns.coerceAtLeast(1)), - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = if (isGridMode) gridInnerHorizontalPadding else 0.dp), contentPadding = adaptiveContentPadding( top = paddingValues.calculateTopPadding(), 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), - horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp), + verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), + horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), showFastScroll = BookshelfConfig.showBookshelfFastScroller ) { items(books, key = { it.bookUrl }) { book -> @@ -621,3 +637,4 @@ fun BookshelfPage( } } } + diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt index dadd56466..dbeec5e34 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt @@ -83,7 +83,7 @@ fun GroupManageSheet( editingGroup = null isEditing = true }, - icon = Icons.Default.Add + imageVector = Icons.Default.Add ) } } @@ -130,3 +130,4 @@ fun GroupManageSheet( } } } + diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt index a99597bfd..846e9fe4d 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt @@ -249,8 +249,11 @@ fun ExploreScreen( TextCard( text = item.bookSourceName, textStyle = LegadoTheme.typography.labelLarge, + backgroundColor = LegadoTheme.colorScheme.cardContainer, + contentColor = LegadoTheme.colorScheme.onCardContainer, + cornerRadius = 8.dp, horizontalPadding = 8.dp, - verticalPadding = 6.dp, + verticalPadding = 8.dp, onClick = { scope.launch { val index = diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt index 992173b19..51c31f874 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt @@ -235,7 +235,7 @@ fun WebServiceSettingBlock( ) { SmallTextButton( text = "复制地址", - icon = Icons.Default.ContentCopy, + imageVector = Icons.Default.ContentCopy, onClick = { onNavigate(PrefClickEvent.CopyUrl(uiState.webServiceAddress)) } @@ -245,7 +245,7 @@ fun WebServiceSettingBlock( SmallTextButton( text = "浏览器打开", - icon = Icons.Default.OpenInBrowser, + imageVector = Icons.Default.OpenInBrowser, onClick = { onNavigate(PrefClickEvent.OpenUrl(uiState.webServiceAddress)) } @@ -254,3 +254,4 @@ fun WebServiceSettingBlock( } } } + diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt index 48de8a0ba..73045b151 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt @@ -143,7 +143,9 @@ class ReplaceRuleActivity : BaseComposeActivity() { } entry { route -> - val viewModel: ReplaceEditViewModel = koinViewModel { parametersOf(route) } + val viewModel: ReplaceEditViewModel = koinViewModel( + key = "replace_edit_${System.identityHashCode(route)}" + ) { parametersOf(route) } ReplaceEditScreen( viewModel = viewModel, diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt index 8ed4230e0..c668e6e2b 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt +++ b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditScreen.kt @@ -42,13 +42,12 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.FilterChip -import androidx.compose.material3.FilterChipDefaults import io.legado.app.ui.widget.components.AppFloatingActionButton 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.TextButton import androidx.compose.material3.animateFloatingActionButton 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.alert.AppAlertDialog 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.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.RoundDropdownMenuItem import io.legado.app.ui.widget.components.text.AppText @@ -115,15 +117,19 @@ fun ReplaceEditScreen( enter = fadeIn(), exit = fadeOut() ) { - IconButton(onClick = { - viewModel.save(onSaveSuccess) - }) { - Icon(Icons.Default.Save, contentDescription = "保存") - } - } - IconButton(onClick = { showMenu = true }) { - Icon(Icons.Default.MoreVert, contentDescription = "更多操作") + TopBarActionButton( + onClick = { + viewModel.save(onSaveSuccess) + }, + imageVector = Icons.Default.Save, + contentDescription = "保存" + ) } + TopBarActionButton( + onClick = { showMenu = true }, + imageVector = Icons.Default.MoreVert, + contentDescription = "更多操作" + ) RoundDropdownMenu( expanded = showMenu, onDismissRequest = { showMenu = false } @@ -158,7 +164,7 @@ fun ReplaceEditScreen( onClick = { viewModel.save(onSaveSuccess) }, tooltipText = "添加" ) { - Icon(Icons.Default.Save, contentDescription = "保存") + AppIcon(Icons.Default.Save, contentDescription = "保存") } }, contentWindowInsets = WindowInsets(0, 0, 0, 0) ) { innerPadding -> @@ -242,53 +248,29 @@ fun ReplaceEditScreen( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start ) { - FilterChip( + ToggleChip( + label = "标题", selected = state.scopeTitle, - onClick = { viewModel.onScopeTitleChange(!state.scopeTitle) }, - label = { AppText("标题") }, - leadingIcon = if (state.scopeTitle) { - { - Icon( - Icons.Default.Check, - contentDescription = "已选择", - Modifier.size(FilterChipDefaults.IconSize) - ) - } - } else null + checkedContentDescription = "已选择", + onToggle = { viewModel.onScopeTitleChange(!state.scopeTitle) } ) Spacer(Modifier.width(8.dp)) - FilterChip( + ToggleChip( + label = "内容", selected = state.scopeContent, - onClick = { viewModel.onScopeContentChange(!state.scopeContent) }, - label = { AppText("内容") }, - leadingIcon = if (state.scopeContent) { - { - Icon( - Icons.Default.Check, - contentDescription = "已选择", - Modifier.size(FilterChipDefaults.IconSize) - ) - } - } else null + checkedContentDescription = "已选择", + onToggle = { viewModel.onScopeContentChange(!state.scopeContent) } ) Spacer(Modifier.weight(1f)) - FilterChip( + ToggleChip( + label = "使用正则", selected = state.isRegex, - onClick = { viewModel.onRegexChange(!state.isRegex) }, - label = { AppText("使用正则") }, - leadingIcon = if (state.isRegex) { - { - Icon( - Icons.Default.Check, - contentDescription = "正则已启用", - Modifier.size(FilterChipDefaults.IconSize) - ) - } - } else null + checkedContentDescription = "正则已启用", + onToggle = { viewModel.onRegexChange(!state.isRegex) } ) } diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt index 887fcc7d3..b32a8a4b1 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt @@ -203,7 +203,7 @@ fun RssFavoritesScreen( SelectionItemCard( title = rssStar.title, subtitle = if (rssStar.group.isNotBlank()) { - "${rssStar.group} • ${rssStar.pubDate ?: ""}" + "${rssStar.group} ?${rssStar.pubDate ?: ""}" } else { rssStar.pubDate }, @@ -232,7 +232,7 @@ fun RssFavoritesScreen( } SmallIconButton( onClick = openAction, - icon = Icons.AutoMirrored.Filled.OpenInNew, + imageVector = Icons.AutoMirrored.Filled.OpenInNew, contentDescription = "Open" ) }, @@ -258,3 +258,4 @@ fun RssFavoritesScreen( } } } + diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt index bfb9d5ae3..fc7113ffd 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceScreen.kt @@ -365,7 +365,7 @@ fun RssSourceScreen( trailingAction = { SmallIconButton( onClick = { showDeleteRuleDialog = item.source }, - icon = Icons.Default.Delete + imageVector = Icons.Default.Delete ) } ) @@ -387,3 +387,4 @@ fun RssSourceScreen( } } } + diff --git a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt index fd180eb87..7d282484a 100644 --- a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt +++ b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt @@ -63,7 +63,7 @@ fun adaptiveContentPadding( bottom: Dp, horizontal: Dp ): 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( top = adjustedTop, bottom = bottom, diff --git a/app/src/main/java/io/legado/app/ui/widget/CollapsibleHeader.kt b/app/src/main/java/io/legado/app/ui/widget/CollapsibleHeader.kt index 98e3879df..1bc32f113 100644 --- a/app/src/main/java/io/legado/app/ui/widget/CollapsibleHeader.kt +++ b/app/src/main/java/io/legado/app/ui/widget/CollapsibleHeader.kt @@ -20,6 +20,7 @@ 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.theme.adaptiveHorizontalPadding import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.text.AppText @@ -37,7 +38,7 @@ fun CollapsibleHeader( GlassCard( modifier = modifier .fillMaxWidth() - .padding(start = 16.dp, top = 4.dp, end = 16.dp), + .adaptiveHorizontalPadding(vertical = 4.dp), shape = MaterialTheme.shapes.medium, containerColor = LegadoTheme.colorScheme.surfaceContainer, onClick = onToggle diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppRadioButtom.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppRadioButtom.kt new file mode 100644 index 000000000..5e83d9cdf --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/components/AppRadioButtom.kt @@ -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 + ) + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt index 7e8e6fdab..5c1f1dfcf 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/GroupManageBottomSheet.kt @@ -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.PaddingValues @@ -120,7 +120,7 @@ private fun GroupItem( ) { SmallTextButton( text = stringResource(id = R.string.ok), - icon = Icons.Default.Check, + imageVector = Icons.Default.Check, onClick = { onUpdateGroup(group, state.text.toString()) expanded = false @@ -130,3 +130,4 @@ private fun GroupItem( } ) } + diff --git a/app/src/main/java/io/legado/app/ui/widget/components/ValueStepper.kt b/app/src/main/java/io/legado/app/ui/widget/components/ValueStepper.kt index 3f0ca7038..c538322fa 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/ValueStepper.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/ValueStepper.kt @@ -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.Row @@ -31,7 +31,7 @@ fun ValueStepper( val newValue = (value.toInt() - 1).toFloat().coerceIn(valueRange) onValueChange(newValue) }, - icon = Icons.Default.Remove + imageVector = Icons.Default.Remove ) TextCard( cornerRadius = 8.dp, @@ -46,7 +46,7 @@ fun ValueStepper( val newValue = (value.toInt() + 1).toFloat().coerceIn(valueRange) onValueChange(newValue) }, - icon = Icons.Default.Add + imageVector = Icons.Default.Add ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt index 9a56b606c..4aa076371 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/MediumIconButton.kt @@ -41,7 +41,7 @@ import top.yukonga.miuix.kmp.basic.Text as MiuixText @Composable fun MediumIconButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { @@ -51,7 +51,7 @@ fun MediumIconButton( modifier = modifier ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -61,7 +61,7 @@ fun MediumIconButton( modifier = modifier ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -71,7 +71,7 @@ fun MediumIconButton( @Composable fun MediumOutlinedIconButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { @@ -83,7 +83,7 @@ fun MediumOutlinedIconButton( backgroundColor = LegadoTheme.colorScheme.surfaceContainerHigh ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -93,7 +93,7 @@ fun MediumOutlinedIconButton( border = ButtonDefaults.outlinedButtonBorder() ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -103,7 +103,7 @@ fun MediumOutlinedIconButton( @Composable fun MediumOutlinedButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, text: String, modifier: Modifier = Modifier, contentDescription: String? = null @@ -120,7 +120,7 @@ fun MediumOutlinedButton( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) MiuixText(text = text) @@ -136,7 +136,7 @@ fun MediumOutlinedButton( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) Text(text = text) @@ -148,7 +148,7 @@ fun MediumOutlinedButton( @Composable fun MediumTonalIconButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { @@ -161,7 +161,7 @@ fun MediumTonalIconButton( backgroundColor = LegadoTheme.colorScheme.surfaceContainer ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -172,7 +172,7 @@ fun MediumTonalIconButton( colors = IconButtonDefaults.filledTonalIconButtonColors() ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -183,7 +183,7 @@ fun MediumTonalIconButton( fun MediumOutlinedIconToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { @@ -208,7 +208,7 @@ fun MediumOutlinedIconToggleButton( backgroundColor = containerColor ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, tint = iconTint ) @@ -220,7 +220,7 @@ fun MediumOutlinedIconToggleButton( modifier = modifier ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt index c7aafc470..166a92cb0 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt @@ -64,7 +64,7 @@ private fun SmallNoMinTouchTarget(content: @Composable () -> Unit) { @Composable fun SmallIconButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, contentDescription: String? = null ) { if (ThemeResolver.isMiuixEngine(composeEngine)) { @@ -72,7 +72,7 @@ fun SmallIconButton( onClick = onClick ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription ) } @@ -84,7 +84,7 @@ fun SmallIconButton( shape = IconButtonDefaults.extraSmallRoundShape, ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(smallIconSize), ) @@ -97,11 +97,10 @@ fun SmallIconButton( @Composable fun SmallOutlinedIconButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { - val isMiuix = ThemeResolver.isMiuixEngine(composeEngine) if (isMiuix) { MiuixIconButton( @@ -110,7 +109,7 @@ fun SmallOutlinedIconButton( backgroundColor = LegadoTheme.colorScheme.surfaceContainer ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(SmallMiuixIconSize) ) @@ -124,7 +123,7 @@ fun SmallOutlinedIconButton( border = ButtonDefaults.outlinedButtonBorder() ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(smallIconSize) ) @@ -137,7 +136,7 @@ fun SmallOutlinedIconButton( @Composable fun SmallTonalIconButton( onClick: () -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { @@ -150,7 +149,7 @@ fun SmallTonalIconButton( backgroundColor = LegadoTheme.colorScheme.surfaceContainer ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(SmallMiuixIconSize) ) @@ -164,7 +163,7 @@ fun SmallTonalIconButton( colors = IconButtonDefaults.filledTonalIconButtonColors() ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(smallIconSize) ) @@ -178,7 +177,7 @@ fun SmallTonalIconButton( fun SmallOutlinedIconToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, contentDescription: String? = null ) { @@ -205,7 +204,7 @@ fun SmallOutlinedIconToggleButton( backgroundColor = containerColor ) { MiuixIcon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, tint = iconTint, modifier = Modifier.size(SmallMiuixIconSize) @@ -233,7 +232,7 @@ fun SmallOutlinedIconToggleButton( shapes = toggleShapes ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(smallIconSize), ) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt index 9c3d49600..918f8f34c 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt @@ -22,7 +22,7 @@ import top.yukonga.miuix.kmp.basic.Text as MiuixText @Composable fun SmallTextButton( text: String, - icon: ImageVector, + imageVector: ImageVector, modifier: Modifier = Modifier, onClick: () -> Unit ) { @@ -44,7 +44,7 @@ fun SmallTextButton( shape = MaterialTheme.shapes.small ) { Icon( - imageVector = icon, + imageVector = imageVector, contentDescription = null, modifier = Modifier.size(16.dp) ) @@ -55,4 +55,4 @@ fun SmallTextButton( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/ToggleChip.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/ToggleChip.kt new file mode 100644 index 000000000..a4d2dee22 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/ToggleChip.kt @@ -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 + ) + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt b/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt index 221416e7f..747ab58a8 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt @@ -181,7 +181,7 @@ fun SelectionItemCard( if (onClickEdit != null) { SmallIconButton( onClick = onClickEdit, - icon = Icons.Default.Edit, + imageVector = Icons.Default.Edit, contentDescription = "Edit" ) } @@ -194,7 +194,7 @@ fun SelectionItemCard( Box { SmallIconButton( onClick = { showMenu = true }, - icon = Icons.Default.MoreVert, + imageVector = Icons.Default.MoreVert, contentDescription = "More" ) RoundDropdownMenu( @@ -272,3 +272,4 @@ fun LazyItemScope.ReorderableSelectionItem( ) } } + diff --git a/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt b/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt index b9e830bb7..0bef92c93 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/heatmap/HeatmapCalendarComponents.kt @@ -19,7 +19,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccessTime import androidx.compose.material.icons.filled.FormatListNumbered import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -43,43 +42,41 @@ import io.legado.app.ui.widget.components.text.AppText import java.time.LocalDate /** - * 热力图日历顶部操作栏 + * 热力图日历弹窗标题 + */ +const val HEATMAP_CALENDAR_TITLE = "时间线" + +/** + * 热力图日历弹窗左侧操作 */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun HeatmapCalendarTopBar( +fun HeatmapCalendarStartAction( currentMode: HeatmapMode, onModeChanged: (HeatmapMode) -> Unit, - onClearDate: () -> Unit, - modifier: Modifier = Modifier ) { - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - AppText("时间线", style = LegadoTheme.typography.titleMedium) + MediumAnimatedActionButton( + checked = currentMode == HeatmapMode.TIME, + onCheckedChange = { + onModeChanged(if (it) HeatmapMode.TIME else HeatmapMode.COUNT) + }, + iconChecked = Icons.Default.AccessTime, + iconUnchecked = Icons.Default.FormatListNumbered, + activeText = "按时长", + inactiveText = "按次数" + ) +} - Row(verticalAlignment = Alignment.CenterVertically) { - MediumAnimatedActionButton( - checked = currentMode == HeatmapMode.TIME, - onCheckedChange = { - onModeChanged(if (it) HeatmapMode.TIME else HeatmapMode.COUNT) - }, - iconChecked = Icons.Default.AccessTime, - iconUnchecked = Icons.Default.FormatListNumbered, - activeText = "按时长", - inactiveText = "按次数" - ) - - Spacer(modifier = Modifier.width(8.dp)) - - MediumOutlinedIconButton( - onClick = onClearDate, - icon = Icons.Outlined.Delete, - ) - } - } +/** + * 热力图日历弹窗右侧操作 + */ +@Composable +fun HeatmapCalendarEndAction( + onClearDate: () -> Unit +) { + MediumOutlinedIconButton( + onClick = onClearDate, + imageVector = Icons.Outlined.Delete, + ) } /** @@ -296,4 +293,4 @@ fun HeatmapLegend( color = Color.Gray ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt index 77a3ea7b8..cc42c9f1e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt @@ -41,7 +41,6 @@ fun AppModalBottomSheet( title: String? = null, startAction: @Composable (() -> Unit)? = null, endAction: @Composable (() -> Unit)? = null, - containerColor: Color = LegadoTheme.colorScheme.surfaceContainer, content: @Composable ColumnScope.() -> Unit ) { if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) { @@ -52,14 +51,15 @@ fun AppModalBottomSheet( startAction = startAction, endAction = endAction, insideMargin = DpSize(16.dp, 12.dp), - backgroundColor = containerColor, onDismissRequest = onDismissRequest, onDismissFinished = onDismissRequest, enableWindowDim = true, allowDismiss = true ) { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .animateContentSize(), content = content ) } @@ -75,7 +75,6 @@ fun AppModalBottomSheet( ModalBottomSheet( onDismissRequest = onDismissRequest, sheetState = sheetState, - containerColor = containerColor, contentColor = colorScheme.onSurface, dragHandle = { BottomSheetDefaults.DragHandle() } ) { diff --git a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt index 9f23196e8..439204cf4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/OptionSheet.kt @@ -32,8 +32,7 @@ fun OptionSheet( AppModalBottomSheet( show = show, title = title, - onDismissRequest = onDismissRequest, - containerColor = LegadoTheme.colorScheme.surface + onDismissRequest = onDismissRequest ) { Column( modifier = Modifier diff --git a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt index 798f7df89..5799f7bad 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt @@ -34,15 +34,19 @@ 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.theme.LegadoTheme import io.legado.app.ui.widget.components.AppFloatingActionButton 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.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.text.AppText import kotlinx.coroutines.launch /** - * 通用编辑数据包装,用于适配不同的规则实体 + * 通用编辑数据包装,用于适配不同的规则 */ data class RuleEditFields( val name: String = "", @@ -80,41 +84,44 @@ fun RuleEditSheet( AppModalBottomSheet( title = title, startAction = { - IconButton(onClick = onDismissRequest) { - Icon( - Icons.Default.Close, - contentDescription = stringResource(R.string.cancel) - ) - } + MediumIconButton( + onClick = onDismissRequest, + imageVector = Icons.Default.Close, + contentDescription = "Close", + ) }, endAction = { - IconButton(onClick = { showMenu = true }) { - Icon(Icons.Default.MoreVert, contentDescription = "More") - } - DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { - DropdownMenuItem( - text = { AppText(stringResource(R.string.copy_rule)) }, - leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) }, - onClick = { - onCopy(getCurrentEntity()) - showMenu = false - } + Box{ + MediumIconButton( + onClick = { showMenu = true }, + imageVector = Icons.Default.MoreVert, + contentDescription = "More" ) - DropdownMenuItem( - text = { AppText(stringResource(R.string.paste_rule)) }, - leadingIcon = { Icon(Icons.Default.ContentPaste, null) }, - onClick = { - scope.launch { - onPaste()?.let { pasted -> - val fields = toFields(pasted) - name = fields.name - rule1 = fields.rule1 - rule2 = fields.rule2 - } + RoundDropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { + RoundDropdownMenuItem( + text = stringResource(R.string.copy_rule), + leadingIcon = { Icon(Icons.AutoMirrored.Filled.NoteAdd, null) }, + onClick = { + onCopy(getCurrentEntity()) + showMenu = false } - showMenu = false - } - ) + ) + RoundDropdownMenuItem( + text = stringResource(R.string.paste_rule), + leadingIcon = { Icon(Icons.Default.ContentPaste, null) }, + onClick = { + scope.launch { + onPaste()?.let { pasted -> + val fields = toFields(pasted) + name = fields.name + rule1 = fields.rule1 + rule2 = fields.rule2 + } + } + showMenu = false + } + ) + } } }, show = show, @@ -132,6 +139,7 @@ fun RuleEditSheet( modifier = Modifier.fillMaxWidth(), value = name, onValueChange = { name = it }, + backgroundColor = LegadoTheme.colorScheme.surface, label = stringResource(R.string.name), singleLine = true ) @@ -139,12 +147,14 @@ fun RuleEditSheet( modifier = Modifier.fillMaxWidth(), value = rule1, onValueChange = { rule1 = it }, + backgroundColor = LegadoTheme.colorScheme.surface, label = label1 ) AppTextField( modifier = Modifier.fillMaxWidth(), value = rule2, onValueChange = { rule2 = it }, + backgroundColor = LegadoTheme.colorScheme.surface, label = label2, minLines = 3 ) @@ -160,4 +170,4 @@ fun RuleEditSheet( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/InputSettingItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/InputSettingItem.kt index 63ad5d893..eefe85ac7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/InputSettingItem.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/InputSettingItem.kt @@ -84,14 +84,14 @@ fun InputSettingItem( ) { SmallTextButton( text = "默认", - icon = Icons.Default.Replay, + imageVector = Icons.Default.Replay, onClick = { state.edit { replace(0, length, defaultValue.toString()) } } ) SmallTextButton( text = "确认", - icon = Icons.Default.Check, + imageVector = Icons.Default.Check, onClick = { onConfirm(state.text.toString()) expanded = false @@ -133,14 +133,14 @@ fun InputSettingItem( ) { SmallTextButton( text = "默认", - icon = Icons.Default.Replay, + imageVector = Icons.Default.Replay, onClick = { state.edit { replace(0, length, defaultValue.toString()) } } ) SmallTextButton( text = "确认", - icon = Icons.Default.Check, + imageVector = Icons.Default.Check, onClick = { onConfirm(state.text.toString()) expanded = false @@ -150,4 +150,4 @@ fun InputSettingItem( } ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SliderSettingItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SliderSettingItem.kt index a1a08a3ad..833350d91 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SliderSettingItem.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/SliderSettingItem.kt @@ -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.AnimatedVisibility @@ -78,7 +78,7 @@ fun SliderSettingItem( MiuixTextField( state = textFieldState, lineLimits = TextFieldLineLimits.SingleLine, - label = "输入数值 (${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})", + label = "杈撳叆鏁板€?(${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})", keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth(), inputTransformation = { @@ -120,16 +120,16 @@ fun SliderSettingItem( horizontalArrangement = Arrangement.End ) { SmallTextButton( - text = if (isInputMode) "滑块" else "输入", - icon = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit, + text = if (isInputMode) "婊戝潡" else "杈撳叆", + imageVector = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit, onClick = { isInputMode = !isInputMode } ) Spacer(Modifier.width(8.dp)) SmallTextButton( - text = "默认", - icon = Icons.Default.RestartAlt, + text = "榛樿", + imageVector = Icons.Default.RestartAlt, onClick = { onValueChange(defaultValue) textFieldState.edit { @@ -163,7 +163,7 @@ fun SliderSettingItem( TextField( state = textFieldState, lineLimits = TextFieldLineLimits.SingleLine, - label = { AppText("输入数值 (${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})") }, + label = { AppText("杈撳叆鏁板€?(${valueRange.start.toInt()}-${valueRange.endInclusive.toInt()})") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier .fillMaxWidth() @@ -212,16 +212,16 @@ fun SliderSettingItem( horizontalArrangement = Arrangement.End ) { SmallTextButton( - text = if (isInputMode) "滑块" else "输入", - icon = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit, + text = if (isInputMode) "婊戝潡" else "杈撳叆", + imageVector = if (isInputMode) Icons.Default.LinearScale else Icons.Default.Edit, onClick = { isInputMode = !isInputMode } ) Spacer(Modifier.width(8.dp)) SmallTextButton( - text = "默认", - icon = Icons.Default.RestartAlt, + text = "榛樿", + imageVector = Icons.Default.RestartAlt, onClick = { onValueChange(defaultValue) textFieldState.edit { @@ -237,4 +237,5 @@ fun SliderSettingItem( } ) } -} \ No newline at end of file +} + diff --git a/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt b/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt index b388dced4..c204fa1b6 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/swipe/SwipeActionContainer.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp +import io.legado.app.ui.theme.LegadoTheme @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -128,7 +129,7 @@ private fun SwipeBackground( targetValue = if (isThresholdReached) action.background else - MaterialTheme.colorScheme.surfaceVariant, + LegadoTheme.colorScheme.surfaceVariant, label = "bgColor" ) @@ -160,7 +161,7 @@ private fun SwipeBackground( tint = if (isThresholdReached) contentColorFor(action.background) else - MaterialTheme.colorScheme.onSurfaceVariant + LegadoTheme.colorScheme.onSurfaceVariant ) } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0432698d..10cdac732 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] accompanistWebview = "0.36.0" -adaptive = "1.3.0-alpha09" -adaptiveLayout = "1.3.0-alpha09" -adaptiveNavigation = "1.3.0-alpha09" +adaptive = "1.3.0-alpha10" +adaptiveLayout = "1.3.0-alpha10" +adaptiveNavigation = "1.3.0-alpha10" animation = "1.10.6" biometric = "1.4.0-alpha06" coilCompose = "2.7.0" @@ -46,8 +46,8 @@ liveeventbus = "1.8.14" navigation3 = "1.0.1" markdownCompose = "0.5.0" markwon = "4.6.2" -material = "1.14.0-alpha10" -material3 = "1.5.0-alpha15" +material = "1.14.0-beta01" +material3 = "1.5.0-alpha17" material3IconsExtended = "1.7.8" materialKolor = "4.1.1" media = "1.7.1"