[优化] 优化界面

This commit is contained in:
HapeLee
2026-04-19 02:34:40 +08:00
parent 85b90f77f7
commit 15f6efc42d
28 changed files with 314 additions and 266 deletions
@@ -160,6 +160,12 @@ interface ReadRecordDao {
@Query("SELECT * FROM readRecordSession WHERE deviceId = :deviceId AND bookName = :bookName AND bookAuthor = :bookAuthor") @Query("SELECT * FROM readRecordSession WHERE deviceId = :deviceId AND bookName = :bookName AND bookAuthor = :bookAuthor")
suspend fun getSessionsByBook(deviceId: String, bookName: String, bookAuthor: String): List<ReadRecordSession> suspend fun getSessionsByBook(deviceId: String, bookName: String, bookAuthor: String): List<ReadRecordSession>
@Query("SELECT * FROM readRecordSession WHERE deviceId = :deviceId AND bookName = :bookName AND bookAuthor = :bookAuthor ORDER BY startTime DESC")
fun getSessionsByBookFlow(deviceId: String, bookName: String, bookAuthor: String): Flow<List<ReadRecordSession>>
@Query("SELECT readTime FROM readRecord WHERE deviceId = :deviceId AND bookName = :bookName AND bookAuthor = :bookAuthor")
fun getReadTimeFlow(deviceId: String, bookName: String, bookAuthor: String): Flow<Long?>
@Delete @Delete
suspend fun deleteDetail(detail: ReadRecordDetail) suspend fun deleteDetail(detail: ReadRecordDetail)
@@ -7,6 +7,7 @@ import io.legado.app.data.dao.ReadRecordDao
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.data.entities.readRecord.ReadRecordSession import io.legado.app.data.entities.readRecord.ReadRecordSession
import io.legado.app.data.entities.readRecord.ReadRecordTimelineDay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import java.util.Date import java.util.Date
@@ -51,6 +52,27 @@ class ReadRecordRepository(
return dao.getAllSessions(getCurrentDeviceId()) return dao.getAllSessions(getCurrentDeviceId())
} }
fun getBookSessions(bookName: String, bookAuthor: String): Flow<List<ReadRecordSession>> {
return dao.getSessionsByBookFlow(getCurrentDeviceId(), bookName, bookAuthor)
}
fun getBookTimelineDays(bookName: String, bookAuthor: String): Flow<List<ReadRecordTimelineDay>> {
return getBookSessions(bookName, bookAuthor).map { sessions ->
sessions.groupBy { DateUtil.format(Date(it.startTime), "yyyy-MM-dd") }
.toSortedMap(compareByDescending { it })
.map { (date, daySessions) ->
ReadRecordTimelineDay(
date = date,
sessions = daySessions.sortedByDescending { it.startTime }
)
}
}
}
fun getBookReadTime(bookName: String, bookAuthor: String): Flow<Long> {
return dao.getReadTimeFlow(getCurrentDeviceId(), bookName, bookAuthor).map { it ?: 0L }
}
suspend fun getMergeCandidates(targetRecord: ReadRecord): List<ReadRecord> { suspend fun getMergeCandidates(targetRecord: ReadRecord): List<ReadRecord> {
return dao.getReadRecordsByNameExcludingAuthor( return dao.getReadRecordsByNameExcludingAuthor(
targetRecord.deviceId, targetRecord.deviceId,
@@ -53,7 +53,7 @@ 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.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem import io.legado.app.ui.widget.components.bookmark.BookmarkItem
import io.legado.app.ui.widget.components.button.TopBarActionButton import io.legado.app.ui.widget.components.button.TopBarActionButton
@@ -190,7 +190,7 @@ fun AllBookmarkScreen(
enter = expandVertically() + fadeIn(), enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut() exit = shrinkVertically() + fadeOut()
) { ) {
SearchBarSection( SearchBar(
query = searchText, query = searchText,
onQueryChange = { viewModel.onSearchQueryChanged(it) }, onQueryChange = { viewModel.onSearchQueryChanged(it) },
placeholder = "搜索...", placeholder = "搜索...",
@@ -258,7 +258,7 @@ fun AllBookmarkScreen(
.animateItem() .animateItem()
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 4.dp), .padding(vertical = 4.dp),
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainer containerColor = LegadoTheme.colorScheme.surfaceContainer
) { ) {
BookmarkGroupHeaderContent( BookmarkGroupHeaderContent(
@@ -10,22 +10,16 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
@@ -35,21 +29,17 @@ import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.FilterList import androidx.compose.material.icons.filled.FilterList
import androidx.compose.material.icons.filled.GridView import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.outlined.FilterAlt import androidx.compose.material.icons.outlined.FilterAlt
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.Surface
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -63,14 +53,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
@@ -81,12 +68,11 @@ import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource import io.legado.app.ui.theme.responsiveHazeSource
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.AnimatedTextButton import io.legado.app.ui.widget.components.button.AnimatedTextButton
import io.legado.app.ui.widget.components.button.TopBarActionButton import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.button.TopBarNavigationButton import io.legado.app.ui.widget.components.button.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.explore.calculateExploreKindRows import io.legado.app.ui.widget.components.explore.calculateExploreKindRows
import io.legado.app.ui.widget.components.explore.ExploreKindMultiTypeItem import io.legado.app.ui.widget.components.explore.ExploreKindMultiTypeItem
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
@@ -254,7 +240,7 @@ fun ExploreShowScreen(
var kindQuery by remember { mutableStateOf("") } var kindQuery by remember { mutableStateOf("") }
SearchBarSection( SearchBar(
query = kindQuery, query = kindQuery,
backgroundColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.5f), backgroundColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.5f),
onQueryChange = { kindQuery = it }, onQueryChange = { kindQuery = it },
@@ -564,7 +564,7 @@ private fun PathNavigationBar(
GlassCard( GlassCard(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
containerColor = LegadoTheme.colorScheme.surfaceContainer, containerColor = LegadoTheme.colorScheme.surfaceContainer,
shape = MaterialTheme.shapes.medium cornerRadius = 12.dp,
) { ) {
LazyRow( LazyRow(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -74,7 +74,7 @@ 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
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBar
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.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
@@ -102,6 +102,7 @@ import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.utils.formatReadDuration
import io.legado.app.utils.StringUtils.formatFriendlyDate import io.legado.app.utils.StringUtils.formatFriendlyDate
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -226,7 +227,7 @@ fun ReadRecordScreen(
modifier = Modifier.adaptiveHorizontalPadding(), modifier = Modifier.adaptiveHorizontalPadding(),
visible = showSearch visible = showSearch
) { ) {
SearchBarSection( SearchBar(
query = state.searchKey ?: "", query = state.searchKey ?: "",
onQueryChange = { viewModel.setSearchKey(it) } onQueryChange = { viewModel.setSearchKey(it) }
) )
@@ -1044,13 +1045,5 @@ fun BookStackView(coverPaths: List<String?>) {
} }
fun formatDuring(mss: Long): String { fun formatDuring(mss: Long): String {
val days = mss / (1000 * 60 * 60 * 24) return formatReadDuration(mss)
val hours = mss % (1000 * 60 * 60 * 24) / (1000 * 60 * 60)
val minutes = mss % (1000 * 60 * 60) / (1000 * 60)
val seconds = mss % (1000 * 60) / 1000
val d = if (days > 0) "${days}" else ""
val h = if (hours > 0) "${hours}小时" else ""
val m = if (minutes > 0) "${minutes}分钟" else ""
val s = if (seconds > 0) "${seconds}" else ""
return if ("$d$h$m$s".isBlank()) "0秒" else "$d$h$m$s"
} }
@@ -1,5 +1,6 @@
package io.legado.app.ui.book.search package io.legado.app.ui.book.search
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
@@ -68,15 +70,15 @@ 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.AppFloatingActionButton import io.legado.app.ui.widget.components.AppFloatingActionButton
import io.legado.app.ui.widget.components.AppSearchBar
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SearchBar
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.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.book.SearchBookListItem import io.legado.app.ui.widget.components.book.SearchBookListItem
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.SmallTextButton import io.legado.app.ui.widget.components.button.SmallTextButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.card.SelectionItemCard import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.button.ToggleChip 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.TopBarActionButton
import io.legado.app.ui.widget.components.button.TopBarAnimatedActionButton import io.legado.app.ui.widget.components.button.TopBarAnimatedActionButton
@@ -84,6 +86,7 @@ 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.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.tabRow.AppTabRow import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -104,8 +107,8 @@ fun SearchScreen(
val state by viewModel.uiState.collectAsStateWithLifecycle() val state by viewModel.uiState.collectAsStateWithLifecycle()
val listState = rememberLazyListState() val listState = rememberLazyListState()
var queryInput by rememberSaveable { mutableStateOf(state.query) } var queryInput by rememberSaveable { mutableStateOf(state.query) }
var searchExpanded by rememberSaveable { mutableStateOf(false) }
var scopeSheetTab by rememberSaveable { mutableStateOf(0) } var scopeSheetTab by rememberSaveable { mutableStateOf(0) }
val showSuggestionPanel = state.showSuggestions
val latestQuery by rememberUpdatedState(state.query) val latestQuery by rememberUpdatedState(state.query)
val scrollBehavior = if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) { val scrollBehavior = if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) {
GlassTopAppBarDefaults.defaultScrollBehavior() GlassTopAppBarDefaults.defaultScrollBehavior()
@@ -149,15 +152,13 @@ fun SearchScreen(
state.hasMore, state.hasMore,
state.isManualStop, state.isManualStop,
state.showSuggestions, state.showSuggestions,
searchExpanded,
) { ) {
if ( if (
shouldLoadMore && shouldLoadMore &&
!state.isSearching && !state.isSearching &&
state.hasMore && state.hasMore &&
!state.isManualStop && !state.isManualStop &&
!state.showSuggestions && !state.showSuggestions
!searchExpanded
) { ) {
viewModel.onIntent(SearchIntent.LoadMore) viewModel.onIntent(SearchIntent.LoadMore)
} }
@@ -203,104 +204,60 @@ fun SearchScreen(
) )
}, },
actions = { actions = {
if (!searchExpanded) { TopBarAnimatedActionButton(
TopBarActionButton( checked = state.isPrecisionSearch,
onClick = { onCheckedChange = { checked ->
viewModel.onIntent(SearchIntent.SetScopeSheetVisible(true)) viewModel.onIntent(SearchIntent.TogglePrecision(checked))
}, },
imageVector = AppIcons.Filter iconChecked = AppIcons.PrecisionSearch,
) iconUnchecked = AppIcons.UnPrecisionSearch,
TopBarActionButton( activeText = stringResource(R.string.precision_search),
onClick = { inactiveText = stringResource(R.string.search),
viewModel.onIntent(SearchIntent.OpenSourceManage) )
}, TopBarActionButton(
imageVector = AppIcons.Settings, onClick = {
) viewModel.onIntent(SearchIntent.SetScopeSheetVisible(true))
} },
imageVector = AppIcons.Filter
)
TopBarActionButton(
onClick = {
viewModel.onIntent(SearchIntent.OpenSourceManage)
},
imageVector = AppIcons.Settings,
)
}, },
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
AppSearchBar( Box(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
query = queryInput, .fillMaxWidth()
onQueryChange = { queryInput = it }, .adaptiveHorizontalPadding()
onSearch = submitSearch,
expanded = searchExpanded,
onExpandedChange = { searchExpanded = it },
label = searchLabel,
placeholder = { Text(text = searchLabel) },
leadingIcon = {
TopBarActionButton(
onClick = {
if (searchExpanded) {
searchExpanded = false
}
},
imageVector = if (searchExpanded) AppIcons.Back else AppIcons.Search,
contentDescription = if (searchExpanded) stringResource(R.string.search) else stringResource(
R.string.search
)
)
},
trailingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
if (queryInput.isNotEmpty()) {
TopBarActionButton(
onClick = {
queryInput = ""
viewModel.onIntent(SearchIntent.UpdateQuery(""))
},
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.clear)
)
}
TopBarAnimatedActionButton(
checked = state.isPrecisionSearch,
onCheckedChange = { checked ->
viewModel.onIntent(SearchIntent.TogglePrecision(checked))
},
iconChecked = AppIcons.PrecisionSearch,
iconUnchecked = AppIcons.UnPrecisionSearch,
activeText = stringResource(R.string.precision_search),
inactiveText = stringResource(R.string.search),
)
if (searchExpanded) {
TopBarActionButton(
onClick = {
viewModel.onIntent(SearchIntent.SetScopeSheetVisible(true))
},
imageVector = AppIcons.Filter,
contentDescription = stringResource(R.string.search_select_group)
)
TopBarActionButton(
onClick = {
viewModel.onIntent(SearchIntent.OpenSourceManage)
},
imageVector = AppIcons.Settings,
contentDescription = stringResource(R.string.book_source_manage)
)
}
}
},
) { ) {
SearchSuggestionPanel( SearchBar(
state = state, query = queryInput,
onUseHistory = { keyword -> onQueryChange = { queryInput = it },
queryInput = keyword onSearch = submitSearch,
viewModel.onIntent(SearchIntent.UseHistoryKeyword(keyword)) placeholder = searchLabel,
searchExpanded = false trailingIcon = {
Row(
modifier = Modifier.padding(end = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (queryInput.isNotEmpty()) {
TopBarActionButton(
onClick = {
queryInput = ""
viewModel.onIntent(SearchIntent.UpdateQuery(""))
},
imageVector = AppIcons.Close,
contentDescription = stringResource(R.string.clear)
)
}
}
}, },
onDeleteHistory = { viewModel.onIntent(SearchIntent.DeleteHistory(it)) },
onOpenBook = {
viewModel.onIntent(SearchIntent.OpenBookshelfBook(it))
searchExpanded = false
},
onClearHistory = {
viewModel.onIntent(SearchIntent.SetClearHistoryDialogVisible(true))
},
modifier = Modifier.fillMaxSize(),
) )
} }
} }
@@ -330,73 +287,113 @@ fun SearchScreen(
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
) { ) {
if (showResultCountCard || showSourceProgressCard) { AnimatedContent(
Spacer(modifier = Modifier.height(6.dp)) targetState = showSuggestionPanel,
Row( label = "SearchBodyTransition",
modifier = Modifier modifier = Modifier.fillMaxSize()
.fillMaxWidth() ) { isSuggestionVisible ->
.padding(horizontal = 36.dp), if (isSuggestionVisible) {
horizontalArrangement = Arrangement.spacedBy(8.dp) SearchSuggestionPanel(
) { state = state,
if (showResultCountCard) { onUseHistory = { keyword ->
TextCard( queryInput = keyword
modifier = Modifier.weight(1f), viewModel.onIntent(SearchIntent.UseHistoryKeyword(keyword))
icon = AppIcons.Search, },
text = "搜索结果 ${state.results.size}" onDeleteHistory = { viewModel.onIntent(SearchIntent.DeleteHistory(it)) },
) onOpenBook = {
} viewModel.onIntent(SearchIntent.OpenBookshelfBook(it))
if (showSourceProgressCard) { },
TextCard( onClearHistory = {
modifier = Modifier.weight(1f), viewModel.onIntent(SearchIntent.SetClearHistoryDialogVisible(true))
icon = AppIcons.Filter, },
text = "已检索源 ${state.processedSources}/${state.totalSources}" modifier = Modifier.fillMaxSize(),
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
if (state.results.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
SearchResultFooter(
isSearching = state.isSearching,
hasMore = state.hasMore,
hasResult = false,
committedQuery = state.committedQuery,
onLoadMore = { viewModel.onIntent(SearchIntent.LoadMore) },
) )
} } else {
} else { Box(modifier = Modifier.fillMaxSize()) {
LazyColumn( Column(modifier = Modifier.fillMaxSize()) {
modifier = Modifier.fillMaxSize(), Spacer(modifier = Modifier.height(8.dp))
state = listState,
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
items(items = state.results, key = { it.bookUrl }) { book ->
val shelfStateFlow = remember(book.bookUrl) {
viewModel.getBookShelfStateFlow(book)
}
SearchBookListItem(
book = book,
shelfState = shelfStateFlow,
onClick = {
viewModel.onIntent(SearchIntent.OpenSearchBook(book))
}
)
}
item { if (state.results.isEmpty()) {
SearchResultFooter( Box(
isSearching = state.isSearching, modifier = Modifier.fillMaxSize(),
hasMore = state.hasMore, contentAlignment = Alignment.Center
hasResult = true, ) {
committedQuery = state.committedQuery, SearchResultFooter(
onLoadMore = { viewModel.onIntent(SearchIntent.LoadMore) }, isSearching = state.isSearching,
) hasMore = state.hasMore,
hasResult = false,
committedQuery = state.committedQuery,
onLoadMore = { viewModel.onIntent(SearchIntent.LoadMore) },
)
}
}
}
if (state.results.isNotEmpty()) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
contentPadding = adaptiveContentPaddingOnlyVertical(
top = 48.dp,
bottom = 8.dp
),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
items(items = state.results, key = { it.bookUrl }) { book ->
val shelfStateFlow = remember(book.bookUrl) {
viewModel.getBookShelfStateFlow(book)
}
SearchBookListItem(
book = book,
shelfState = shelfStateFlow,
onClick = {
viewModel.onIntent(SearchIntent.OpenSearchBook(book))
}
)
}
item {
SearchResultFooter(
isSearching = state.isSearching,
hasMore = state.hasMore,
hasResult = true,
committedQuery = state.committedQuery,
onLoadMore = { viewModel.onIntent(SearchIntent.LoadMore) },
)
}
}
}
TopFloatingStickyItem(
item = if (showResultCountCard || showSourceProgressCard) {
SearchFloatingSummary(
resultText = if (showResultCountCard) "结果 ${state.results.size}" else null,
sourceText = if (showSourceProgressCard) " · 进度 ${state.processedSources}/${state.totalSources}" else null,
)
} else {
null
},
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 6.dp),
) { summary ->
NormalCard(
cornerRadius = 16.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
) {
summary.resultText?.let { text ->
AppText(text = text, style = LegadoTheme.typography.labelSmallEmphasized)
}
summary.sourceText?.let { text ->
AppText(text = text, style = LegadoTheme.typography.labelSmallEmphasized)
}
}
}
}
} }
} }
} }
@@ -520,6 +517,11 @@ fun SearchScreen(
} }
} }
private data class SearchFloatingSummary(
val resultText: String?,
val sourceText: String?,
)
@Composable @Composable
private fun SearchSuggestionPanel( private fun SearchSuggestionPanel(
state: SearchUiState, state: SearchUiState,
@@ -565,7 +567,10 @@ private fun SearchSuggestionPanel(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(
modifier = Modifier.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
AppIcon(AppIcons.History, contentDescription = null) AppIcon(AppIcons.History, contentDescription = null)
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
AppText( AppText(
@@ -636,9 +641,16 @@ private fun SearchResultFooter(
) { ) {
when { when {
isSearching -> { isSearching -> {
Row(verticalAlignment = Alignment.CenterVertically) { Row(
CircularProgressIndicator(modifier = Modifier.width(18.dp), strokeWidth = 2.dp) verticalAlignment = Alignment.CenterVertically,
Spacer(modifier = Modifier.width(8.dp)) horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Box(
modifier = Modifier.size(18.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
AppText(text = stringResource(R.string.is_loading)) AppText(text = stringResource(R.string.is_loading))
} }
} }
@@ -59,7 +59,7 @@ import io.legado.app.ui.theme.LegadoTheme
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
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.MediumOutlinedButton import io.legado.app.ui.widget.components.button.MediumOutlinedButton
import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton import io.legado.app.ui.widget.components.button.SmallAnimatedActionButton
import io.legado.app.ui.widget.components.button.SmallIconButton import io.legado.app.ui.widget.components.button.SmallIconButton
@@ -167,7 +167,7 @@ fun SearchContentScreen(
Box( Box(
modifier = Modifier.adaptiveHorizontalPadding() modifier = Modifier.adaptiveHorizontalPadding()
) { ) {
SearchBarSection( SearchBar(
query = searchQuery, query = searchQuery,
scrollState = listState, scrollState = listState,
onQueryChange = { viewModel.onQueryChange(it) } onQueryChange = { viewModel.onQueryChange(it) }
@@ -104,6 +104,7 @@ import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem import io.legado.app.ui.widget.components.bookmark.BookmarkItem
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.divider.PillHeaderDivider import io.legado.app.ui.widget.components.divider.PillHeaderDivider
@@ -950,13 +951,13 @@ private fun StatusIcon(
} }
"SUCCESS_WORD_COUNT" -> { "SUCCESS_WORD_COUNT" -> {
Surface( NormalCard(
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
border = BorderStroke( border = BorderStroke(
1.dp, 1.dp,
LegadoTheme.colorScheme.outlineVariant LegadoTheme.colorScheme.outlineVariant
), ),
color = Color.Transparent containerColor = Color.Transparent
) { ) {
if (wordCount != null) { if (wordCount != null) {
AppText( AppText(
@@ -36,7 +36,9 @@ import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcon
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
@@ -87,9 +89,8 @@ fun CoverManageSheet(
) { ) {
items(coverList) { path -> items(coverList) { path ->
Box { Box {
Surface( NormalCard(
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp
tonalElevation = 2.dp
) { ) {
AsyncImage( AsyncImage(
model = path, model = path,
@@ -122,16 +123,16 @@ fun CoverManageSheet(
} }
item { item {
Surface( NormalCard(
onClick = { showFilePicker = true }, onClick = { showFilePicker = true },
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
color = MaterialTheme.colorScheme.surfaceContainerHigh, containerColor = LegadoTheme.colorScheme.surfaceContainerHigh,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(0.7f) .aspectRatio(0.7f)
) { ) {
Box(contentAlignment = Alignment.Center) { Box(contentAlignment = Alignment.Center) {
Icon( AppIcon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
contentDescription = "Add", contentDescription = "Add",
modifier = Modifier.size(32.dp), modifier = Modifier.size(32.dp),
@@ -32,6 +32,7 @@ import coil.compose.AsyncImage
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -80,10 +81,10 @@ fun BackgroundImageManageSheet(
) { ) {
if (currentPath.isNullOrBlank()) { if (currentPath.isNullOrBlank()) {
Surface( NormalCard(
onClick = { showFilePicker = true }, onClick = { showFilePicker = true },
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
color = LegadoTheme.colorScheme.surfaceContainerHigh, containerColor = LegadoTheme.colorScheme.surfaceContainerHigh,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(16f / 9f) .aspectRatio(16f / 9f)
@@ -104,9 +105,8 @@ fun BackgroundImageManageSheet(
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .padding(16.dp)
) { ) {
Surface( NormalCard(
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
tonalElevation = 2.dp
) { ) {
AsyncImage( AsyncImage(
model = currentPath, model = currentPath,
@@ -92,7 +92,7 @@ fun ThemeListDialog(
}, },
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
shape = RoundedCornerShape(20.dp), cornerRadius = 20.dp,
containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) { containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) {
MaterialTheme.colorScheme.secondaryContainer MaterialTheme.colorScheme.secondaryContainer
} else { } else {
@@ -315,7 +315,6 @@ fun MainScreen(
AppNavigationBar( AppNavigationBar(
modifier = Modifier modifier = Modifier
.regularHazeEffect(state = hazeState) .regularHazeEffect(state = hazeState)
.height(if (isUnlabeled) 64.dp else 80.dp)
) { ) {
destinations.forEachIndexed { index, destination -> destinations.forEachIndexed { index, destination ->
val selected = pagerState.targetPage == index val selected = pagerState.targetPage == index
@@ -77,6 +77,7 @@ import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
@@ -568,9 +569,9 @@ fun BookshelfScreen(
if (uiState.isLoading) { if (uiState.isLoading) {
Dialog(onDismissRequest = {}) { Dialog(onDismissRequest = {}) {
Surface( NormalCard(
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
color = MaterialTheme.colorScheme.surfaceContainerHigh containerColor = LegadoTheme.colorScheme.surfaceContainerHigh
) { ) {
Column( Column(
modifier = Modifier.padding(24.dp), modifier = Modifier.padding(24.dp),
@@ -345,7 +345,7 @@ fun ExploreSourceHeader(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 4.dp), .padding(vertical = 4.dp),
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
containerColor = containerColor containerColor = containerColor
) { ) {
ListItem( ListItem(
@@ -180,7 +180,8 @@ fun AppTheme(
surfaceContainer = miuixColorScheme.surfaceContainer, surfaceContainer = miuixColorScheme.surfaceContainer,
surfaceContainerHigh = miuixColorScheme.surfaceContainerHigh, surfaceContainerHigh = miuixColorScheme.surfaceContainerHigh,
surfaceContainerHighest = miuixColorScheme.surfaceContainerHighest, surfaceContainerHighest = miuixColorScheme.surfaceContainerHighest,
surfaceContainerLow = miuixColorScheme.secondaryContainer, surfaceContainerLow = miuixColorScheme.secondaryContainer.copy(alpha = 0.32f)
.compositeOver(miuixColorScheme.surface),
surfaceContainerLowest = miuixColorScheme.background, surfaceContainerLowest = miuixColorScheme.background,
primaryFixed = miuixColorScheme.primaryContainer, primaryFixed = miuixColorScheme.primaryContainer,
@@ -39,7 +39,7 @@ fun CollapsibleHeader(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.adaptiveHorizontalPadding(vertical = 4.dp), .adaptiveHorizontalPadding(vertical = 4.dp),
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainer, containerColor = LegadoTheme.colorScheme.surfaceContainer,
onClick = onToggle onClick = onToggle
) { ) {
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -15,6 +16,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.ImeAction
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.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
@@ -26,10 +28,11 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SearchBarSection( fun SearchBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
query: String, query: String,
onQueryChange: (String) -> Unit, onQueryChange: (String) -> Unit,
onSearch: (String) -> Unit = {},
placeholder: String = "搜索...", placeholder: String = "搜索...",
leadingIcon: @Composable (() -> Unit)? = { leadingIcon: @Composable (() -> Unit)? = {
AppIcon( AppIcon(
@@ -79,10 +82,15 @@ fun SearchBarSection(
placeholder = { AppText(placeholder) }, placeholder = { AppText(placeholder) },
leadingIcon = leadingIcon, leadingIcon = leadingIcon,
trailingIcon = trailingIcon, trailingIcon = trailingIcon,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
onKeyboardAction = {
onSearch(textFieldState.text.toString())
},
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
backgroundColor = resolvedBackgroundColor, backgroundColor = resolvedBackgroundColor,
miuixUseSearchBarInputField = true, miuixUseSearchBarInputField = true,
miuixSearchBarLabel = placeholder miuixSearchBarLabel = placeholder,
miuixOnSearch = onSearch,
) )
} else { } else {
Surface( Surface(
@@ -98,6 +106,10 @@ fun SearchBarSection(
placeholder = { AppText(placeholder) }, placeholder = { AppText(placeholder) },
leadingIcon = leadingIcon, leadingIcon = leadingIcon,
trailingIcon = trailingIcon, trailingIcon = trailingIcon,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
onKeyboardAction = {
onSearch(textFieldState.text.toString())
},
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
backgroundColor = Color.Transparent backgroundColor = Color.Transparent
) )
@@ -17,6 +17,8 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Shuffle
import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -59,7 +61,7 @@ fun SearchBookListItem(
BookShelfState.SAME_NAME_AUTHOR -> { BookShelfState.SAME_NAME_AUTHOR -> {
{ {
androidx.compose.material3.Icon( androidx.compose.material3.Icon(
imageVector = Icons.Default.Warning, imageVector = Icons.Default.Shuffle,
contentDescription = "同名书籍", contentDescription = "同名书籍",
modifier = Modifier.size(12.dp), modifier = Modifier.size(12.dp),
) )
@@ -1,17 +1,20 @@
package io.legado.app.ui.widget.components.card package io.legado.app.ui.widget.components.card
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
@@ -25,12 +28,12 @@ import top.yukonga.miuix.kmp.basic.CardDefaults as MiuixCardDefaults
private fun BaseCard( private fun BaseCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape, onLongClick: (() -> Unit)? = null,
cornerRadius: Dp = MiuixCardDefaults.CornerRadius, cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
pressFeedbackType: PressFeedbackType = PressFeedbackType.None, pressFeedbackType: PressFeedbackType = PressFeedbackType.None,
containerColor: Color? = null, containerColor: Color? = null,
contentColor: Color? = null, contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(), elevation: Dp = 0.dp,
border: BorderStroke? = null, border: BorderStroke? = null,
alpha: Float = 1f, alpha: Float = 1f,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
@@ -47,6 +50,7 @@ private fun BaseCard(
pressFeedbackType = pressFeedbackType, pressFeedbackType = pressFeedbackType,
showIndication = true, showIndication = true,
onClick = onClick, onClick = onClick,
onLongPress = onLongClick,
content = content, content = content,
colors = colors colors = colors
) )
@@ -67,25 +71,26 @@ private fun BaseCard(
disabledContainerColor = LegadoTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f), disabledContainerColor = LegadoTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f),
disabledContentColor = LegadoTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f) disabledContentColor = LegadoTheme.colorScheme.onSecondaryContainer.copy(alpha = alpha * 0.38f)
) )
if (onClick != null) { val clickableModifier = if (onClick != null || onLongClick != null) {
Card( modifier
modifier = modifier, .clip(RoundedCornerShape(cornerRadius))
shape = RoundedCornerShape(cornerRadius), .combinedClickable(
colors = colors, onClick = { onClick?.invoke() },
elevation = elevation, onLongClick = onLongClick
border = border, )
onClick = onClick,
content = content
)
} else { } else {
Card( modifier
modifier = modifier, }
shape = RoundedCornerShape(cornerRadius), Surface(
colors = colors, modifier = clickableModifier,
elevation = elevation, shape = RoundedCornerShape(cornerRadius),
border = border, color = colors.containerColor,
content = content contentColor = colors.contentColor,
) tonalElevation = 0.dp,
shadowElevation = elevation,
border = border
) {
Column(content = content)
} }
} }
} }
@@ -95,19 +100,19 @@ private fun BaseCard(
fun GlassCard( fun GlassCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape, onLongClick: (() -> Unit)? = null,
cornerRadius: Dp = MiuixCardDefaults.CornerRadius, cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
pressFeedbackType: PressFeedbackType = PressFeedbackType.None, pressFeedbackType: PressFeedbackType = PressFeedbackType.None,
containerColor: Color? = null, containerColor: Color? = null,
contentColor: Color? = null, contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(), elevation: Dp = 0.dp,
border: BorderStroke? = null, border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
BaseCard( BaseCard(
modifier = modifier, modifier = modifier,
onClick = onClick, onClick = onClick,
shape = shape, onLongClick = onLongClick,
cornerRadius = cornerRadius, cornerRadius = cornerRadius,
pressFeedbackType = pressFeedbackType, pressFeedbackType = pressFeedbackType,
containerColor = containerColor, containerColor = containerColor,
@@ -124,19 +129,19 @@ fun GlassCard(
fun NormalCard( fun NormalCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
shape: Shape = CardDefaults.shape, onLongClick: (() -> Unit)? = null,
cornerRadius: Dp = MiuixCardDefaults.CornerRadius, cornerRadius: Dp = MiuixCardDefaults.CornerRadius,
pressFeedbackType: PressFeedbackType = PressFeedbackType.None, pressFeedbackType: PressFeedbackType = PressFeedbackType.None,
containerColor: Color? = null, containerColor: Color? = null,
contentColor: Color? = null, contentColor: Color? = null,
elevation: CardElevation = CardDefaults.cardElevation(), elevation: Dp = 0.dp,
border: BorderStroke? = null, border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit content: @Composable ColumnScope.() -> Unit
) { ) {
BaseCard( BaseCard(
modifier = modifier, modifier = modifier,
onClick = onClick, onClick = onClick,
shape = shape, onLongClick = onLongClick,
cornerRadius = cornerRadius, cornerRadius = cornerRadius,
pressFeedbackType = pressFeedbackType, pressFeedbackType = pressFeedbackType,
containerColor = containerColor, containerColor = containerColor,
@@ -19,7 +19,6 @@ import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
@@ -88,9 +87,9 @@ fun SelectionItemCard(
onClick = onToggleSelection, onClick = onToggleSelection,
modifier = modifier modifier = modifier
.fillMaxWidth(), .fillMaxWidth(),
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
containerColor = animatedContainerColor, containerColor = animatedContainerColor,
elevation = CardDefaults.cardElevation(defaultElevation = elevation) elevation = elevation
) { ) {
SelectionItemCardContent( SelectionItemCardContent(
title = title, title = title,
@@ -36,12 +36,12 @@ fun ExploreKindItem(
LocalMinimumInteractiveComponentSize provides Dp.Unspecified LocalMinimumInteractiveComponentSize provides Dp.Unspecified
) { ) {
val shape = MaterialTheme.shapes.medium val cornerRadius = 12.dp
if (isClickable) { if (isClickable) {
GlassCard( GlassCard(
onClick = onClick, onClick = onClick,
shape = shape, cornerRadius = cornerRadius,
containerColor = backgroundColor, containerColor = backgroundColor,
contentColor = LegadoTheme.colorScheme.onSurface, contentColor = LegadoTheme.colorScheme.onSurface,
modifier = modifier modifier = modifier
@@ -54,7 +54,7 @@ fun ExploreKindItem(
} }
} else { } else {
GlassCard( GlassCard(
shape = shape, cornerRadius = cornerRadius,
containerColor = backgroundColor, containerColor = backgroundColor,
contentColor = LegadoTheme.colorScheme.primary, contentColor = LegadoTheme.colorScheme.primary,
modifier = modifier, modifier = modifier,
@@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.NormalCard
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.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -58,14 +59,14 @@ fun RowScope.OptionCard(
text: String, text: String,
onClick: () -> Unit onClick: () -> Unit
) { ) {
Surface( NormalCard(
onClick = onClick, onClick = onClick,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(100.dp), .height(100.dp),
shape = MaterialTheme.shapes.medium, cornerRadius = 12.dp,
color = LegadoTheme.colorScheme.surfaceContainerLow, containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
tonalElevation = 2.dp elevation = 2.dp
) { ) {
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -21,7 +21,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.TopBarActionButton import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.button.TopBarNavigationButton import io.legado.app.ui.widget.components.button.TopBarNavigationButton
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
@@ -107,7 +107,7 @@ fun <T> DynamicTopAppBar(
enter = expandVertically() + fadeIn(), enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut() exit = shrinkVertically() + fadeOut()
) { ) {
SearchBarSection( SearchBar(
query = state.searchKey, query = state.searchKey,
onQueryChange = onSearchQueryChange, onQueryChange = onSearchQueryChange,
placeholder = searchPlaceholder, placeholder = searchPlaceholder,
@@ -246,6 +246,7 @@
<string name="threads_num_title">更新和搜索线程数</string> <string name="threads_num_title">更新和搜索线程数</string>
<string name="change_icon">切换图标</string> <string name="change_icon">切换图标</string>
<string name="remove_from_bookshelf">已在书架</string> <string name="remove_from_bookshelf">已在书架</string>
<string name="already_in_bookshelf">已在书架</string>
<string name="start_read">开始阅读</string> <string name="start_read">开始阅读</string>
<string name="data_loading">加载数据中…</string> <string name="data_loading">加载数据中…</string>
<string name="load_error_retry">加载失败</string> <string name="load_error_retry">加载失败</string>
@@ -395,6 +396,7 @@
<string name="imm_navigation_bar_s">导航栏颜色透明</string> <string name="imm_navigation_bar_s">导航栏颜色透明</string>
<string name="add_to_bookshelf">放入书架</string> <string name="add_to_bookshelf">放入书架</string>
<string name="click_to_remove">点击删除</string> <string name="click_to_remove">点击删除</string>
<string name="long_press_group">长按分组</string>
<string name="continue_read">继续阅读</string> <string name="continue_read">继续阅读</string>
<string name="cover_path">封面地址</string> <string name="cover_path">封面地址</string>
<string name="page_anim_cover">覆盖</string> <string name="page_anim_cover">覆盖</string>
@@ -223,6 +223,7 @@
<string name="threads_num_title">更新/搜尋線程數,太多會卡頓</string> <string name="threads_num_title">更新/搜尋線程數,太多會卡頓</string>
<string name="change_icon">切換圖標</string> <string name="change_icon">切換圖標</string>
<string name="remove_from_bookshelf">刪除書輯</string> <string name="remove_from_bookshelf">刪除書輯</string>
<string name="already_in_bookshelf">已在書架</string>
<string name="start_read">開始閲讀</string> <string name="start_read">開始閲讀</string>
<string name="data_loading">數據載入中…</string> <string name="data_loading">數據載入中…</string>
<string name="load_error_retry">載入失敗,點擊重試</string> <string name="load_error_retry">載入失敗,點擊重試</string>
@@ -225,6 +225,7 @@
<string name="threads_num_title">更新和搜尋執行緒數,太多會卡頓</string> <string name="threads_num_title">更新和搜尋執行緒數,太多會卡頓</string>
<string name="change_icon">切換圖示</string> <string name="change_icon">切換圖示</string>
<string name="remove_from_bookshelf">刪除書籍</string> <string name="remove_from_bookshelf">刪除書籍</string>
<string name="already_in_bookshelf">已在書架</string>
<string name="start_read">開始閱讀</string> <string name="start_read">開始閱讀</string>
<string name="data_loading">載入資料中…</string> <string name="data_loading">載入資料中…</string>
<string name="load_error_retry">載入失敗,點擊重試</string> <string name="load_error_retry">載入失敗,點擊重試</string>
@@ -373,6 +374,7 @@
<string name="imm_navigation_bar_s">導航欄顏色透明</string> <string name="imm_navigation_bar_s">導航欄顏色透明</string>
<string name="add_to_bookshelf">放入書架</string> <string name="add_to_bookshelf">放入書架</string>
<string name="click_to_remove">點擊刪除</string> <string name="click_to_remove">點擊刪除</string>
<string name="long_press_group">長按分組</string>
<string name="continue_read">繼續閱讀</string> <string name="continue_read">繼續閱讀</string>
<string name="cover_path">封面地址</string> <string name="cover_path">封面地址</string>
<string name="page_anim_cover">覆蓋</string> <string name="page_anim_cover">覆蓋</string>
+2
View File
@@ -249,6 +249,7 @@
<string name="threads_num_title">Number of concurrent tasks</string> <string name="threads_num_title">Number of concurrent tasks</string>
<string name="change_icon">Change icon</string> <string name="change_icon">Change icon</string>
<string name="remove_from_bookshelf">In library</string> <string name="remove_from_bookshelf">In library</string>
<string name="already_in_bookshelf">Already in Bookshelf</string>
<string name="start_read">Start reading</string> <string name="start_read">Start reading</string>
<string name="data_loading">Loading…</string> <string name="data_loading">Loading…</string>
<string name="load_error_retry">Load failed, tap to retry</string> <string name="load_error_retry">Load failed, tap to retry</string>
@@ -398,6 +399,7 @@
<string name="imm_navigation_bar_s">The navigation bar becomes transparent</string> <string name="imm_navigation_bar_s">The navigation bar becomes transparent</string>
<string name="add_to_bookshelf">Add to Bookshelf</string> <string name="add_to_bookshelf">Add to Bookshelf</string>
<string name="click_to_remove">Tap to remove</string> <string name="click_to_remove">Tap to remove</string>
<string name="long_press_group">Long press to group</string>
<string name="continue_read">Continue reading</string> <string name="continue_read">Continue reading</string>
<string name="cover_path">Cover path</string> <string name="cover_path">Cover path</string>
<string name="page_anim_cover">Cover</string> <string name="page_anim_cover">Cover</string>