[新增] 由Compose重写的书签界面,现在可以进行模糊搜索书签,可以展开与折叠书籍

This commit is contained in:
HapeLee
2025-12-03 01:15:52 +08:00
parent a28af43107
commit e8ebfa2c0e
8 changed files with 585 additions and 121 deletions
+4 -1
View File
@@ -33,6 +33,7 @@ import io.legado.app.data.entities.rule.ContentRule
import io.legado.app.data.entities.rule.ExploreRule
import io.legado.app.data.entities.rule.SearchRule
import io.legado.app.di.appDatabaseModule
import io.legado.app.di.bookmarkModule
import io.legado.app.di.exploreModule
import io.legado.app.di.readRecordModule
import io.legado.app.help.AppFreezeMonitor
@@ -63,6 +64,7 @@ import io.legado.app.utils.getPrefString
import io.legado.app.utils.isDebuggable
import kotlinx.coroutines.launch
import org.chromium.base.ThreadUtils
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.GlobalContext.startKoin
import splitties.init.appCtx
import splitties.systemservices.notificationManager
@@ -77,7 +79,8 @@ class App : Application() {
override fun onCreate() {
startKoin {
modules(appDatabaseModule, readRecordModule, exploreModule)
androidContext(this@App)
modules(appDatabaseModule, readRecordModule, exploreModule, bookmarkModule)
}
if (getPrefString("app_theme", "0") == "12") {
if (AppConfig.customMode == "accent")
@@ -48,6 +48,17 @@ interface BookmarkDao {
)
fun search(bookName: String, bookAuthor: String, key: String): List<Bookmark>
// 模糊搜索
@Query("""
SELECT * FROM bookmarks
WHERE (bookName LIKE '%'||:query||'%'
OR bookText LIKE '%'||:query||'%'
OR chapterName LIKE '%'||:query||'%'
OR content LIKE '%'||:query||'%')
ORDER BY bookName COLLATE LOCALIZED, bookAuthor COLLATE LOCALIZED, chapterIndex, chapterPos
""")
fun flowSearchAll(query: String): Flow<List<Bookmark>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg bookmark: Bookmark)
@@ -0,0 +1,16 @@
package io.legado.app.di
import io.legado.app.ui.book.bookmark.AllBookmarkViewModel
import org.koin.android.ext.koin.androidApplication
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
val bookmarkModule = module {
viewModel {
AllBookmarkViewModel(
androidApplication(),
get()
)
}
}
@@ -1,84 +1,24 @@
package io.legado.app.ui.book.bookmark
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppLog
import io.legado.app.data.appDb
import io.legado.app.data.entities.Bookmark
import io.legado.app.databinding.ActivityAllBookmarkBinding
import io.legado.app.ui.file.HandleFileContract
import io.legado.app.utils.applyNavigationBarPadding
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import io.legado.app.base.BaseComposeActivity
/**
* 所有书签
*/
class AllBookmarkActivity : VMBaseActivity<ActivityAllBookmarkBinding, AllBookmarkViewModel>(),
BookmarkAdapter.Callback {
override val viewModel by viewModels<AllBookmarkViewModel>()
override val binding by viewBinding(ActivityAllBookmarkBinding::inflate)
private val adapter by lazy {
BookmarkAdapter(this, this)
}
private val exportDir = registerForActivityResult(HandleFileContract()) {
it.uri?.let { uri ->
when (it.requestCode) {
1 -> viewModel.exportBookmark(uri)
2 -> viewModel.exportBookmarkMd(uri)
}
class AllBookmarkActivity : BaseComposeActivity() {
@Composable
override fun Content() {
MaterialTheme {
AllBookmarkScreen(
onBack = { finish() }
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initView()
lifecycleScope.launch {
appDb.bookmarkDao.flowAll().catch {
AppLog.put("所有书签界面获取数据失败\n${it.localizedMessage}", it)
}.flowOn(IO).collect {
adapter.setItems(it)
}
}
}
private fun initView() {
setSupportActionBar(binding.topBar)
binding.recyclerView.addItemDecoration(BookmarkDecoration(this,adapter))
binding.recyclerView.adapter = adapter
binding.recyclerView.applyNavigationBarPadding()
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.bookmark, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_export -> exportDir.launch {
requestCode = 1
}
R.id.menu_export_md -> exportDir.launch {
requestCode = 2
}
}
return super.onCompatOptionsItemSelected(item)
}
override fun onItemClick(bookmark: Bookmark, position: Int) {
showDialogFragment(BookmarkDialog(bookmark, position))
}
}
@@ -0,0 +1,394 @@
package io.legado.app.ui.book.bookmark
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.UnfoldLess
import androidx.compose.material.icons.filled.UnfoldMore
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.widget.components.AnimatedTextLine
import io.legado.app.ui.widget.components.SearchBarSection
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun AllBookmarkScreen(
viewModel: AllBookmarkViewModel = koinViewModel(),
onBack: () -> Unit,
) {
val context = LocalContext.current
val searchText by viewModel.searchQuery.collectAsState()
val bookmarksGrouped by viewModel.bookmarksState.collectAsState()
val collapsedGroups by viewModel.collapsedGroups.collectAsState()
var showMenu by remember { mutableStateOf(false) }
var showSearch by remember { mutableStateOf(false) }
var editingBookmark by remember { mutableStateOf<Bookmark?>(null) }
var showBottomSheet by remember { mutableStateOf(false) }
var pendingExportIsMd by remember { mutableStateOf(false) }
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val allKeys = bookmarksGrouped.keys
val isAllCollapsed = allKeys.isNotEmpty() && collapsedGroups.containsAll(allKeys)
val exportLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree()
) { uri: Uri? ->
uri?.let {
viewModel.exportBookmark(it, pendingExportIsMd)
Toast.makeText(context, "开始导出...", Toast.LENGTH_SHORT).show()
}
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
Column {
MediumTopAppBar(
title = {
val text = if (showSearch) "搜索" else "所有书签"
AnimatedTextLine(
text = text,
style = MaterialTheme.typography.titleLarge
)
},
scrollBehavior = scrollBehavior,
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
actions = {
if (bookmarksGrouped.isNotEmpty()) {
IconButton(onClick = { viewModel.toggleAllCollapse(allKeys) }) {
Icon(
imageVector = if (isAllCollapsed) Icons.Default.UnfoldMore else Icons.Default.UnfoldLess,
contentDescription = null
)
}
}
IconButton(onClick = {
showSearch = !showSearch
if (!showSearch) {
viewModel.onSearchQueryChanged("")
}
}) {
Icon(Icons.Default.Search, contentDescription = "Search")
}
IconButton(onClick = { showMenu = true }) {
Icon(Icons.Default.MoreVert, contentDescription = "Menu")
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
text = { Text("导出 JSON") },
onClick = {
showMenu = false
pendingExportIsMd = false
exportLauncher.launch(null)
}
)
DropdownMenuItem(
text = { Text("导出 Markdown") },
onClick = {
showMenu = false
pendingExportIsMd = true
exportLauncher.launch(null)
}
)
}
}
)
AnimatedVisibility(
visible = showSearch,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
SearchBarSection(
query = searchText,
onQueryChange = { viewModel.onSearchQueryChanged(it) },
placeholder = "模糊搜索"
)
}
}
}
) { paddingValues ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
bookmarksGrouped.forEach { (headerTitle, bookmarks) ->
val isCollapsed = collapsedGroups.contains(headerTitle)
item(key = headerTitle) {
Column {
BookmarkHeader(
text = headerTitle,
isCollapsed = isCollapsed,
onToggle = { viewModel.toggleGroupCollapse(headerTitle) }
)
AnimatedVisibility(
visible = !isCollapsed,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
Column(
modifier = Modifier
.animateContentSize()
) {
bookmarks.forEach { bookmark ->
BookmarkItem(
bookmark = bookmark,
modifier = Modifier
.animateItem()
.fillMaxWidth(),
onClick = {
editingBookmark = bookmark
showBottomSheet = true
}
)
}
}
}
}
}
}
}
if (showBottomSheet && editingBookmark != null) {
BookmarkEditSheet(
bookmark = editingBookmark!!,
onDismiss = {
showBottomSheet = false
editingBookmark = null
},
onSave = { updatedBookmark ->
viewModel.updateBookmark(updatedBookmark)
showBottomSheet = false
},
onDelete = { bookmarkToDelete ->
viewModel.deleteBookmark(bookmarkToDelete)
showBottomSheet = false
}
)
}
}
}
@Composable
fun BookmarkHeader(
text: String,
isCollapsed: Boolean,
onToggle: () -> Unit
) {
Surface(
color = MaterialTheme.colorScheme.surfaceContainer,
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onToggle)
) {
Row(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 标题
Text(
text = text,
style = MaterialTheme.typography.titleSmall.copy(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
),
modifier = Modifier.weight(1f)
)
val rotation by animateFloatAsState(
targetValue = if (isCollapsed) 0f else 180f,
label = "arrowRotation"
)
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
modifier = Modifier.rotate(rotation),
tint = MaterialTheme.colorScheme.primary
)
}
}
}
@Composable
fun BookmarkItem(
bookmark: Bookmark,
modifier: Modifier = Modifier,
onClick: () -> Unit) {
Column(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(
text = bookmark.chapterName,
style = MaterialTheme.typography.bodySmall,
color = Color.Gray
)
if (bookmark.bookText.isNotEmpty()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = bookmark.bookText,
style = MaterialTheme.typography.bodyMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
if (bookmark.content.isNotEmpty()) {
Text(
text = bookmark.content,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookmarkEditSheet(
bookmark: Bookmark,
onDismiss: () -> Unit,
onSave: (Bookmark) -> Unit,
onDelete: (Bookmark) -> Unit
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var showDeleteConfirmDialog by remember { mutableStateOf(false) }
var bookText by remember { mutableStateOf(bookmark.bookText) }
var content by remember { mutableStateOf(bookmark.content) }
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.navigationBarsPadding()
) {
Text(
text = bookmark.chapterName,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField(
value = bookText,
onValueChange = { bookText = it },
label = { Text("原文") },
modifier = Modifier.fillMaxWidth(),
maxLines = 10
)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = content,
onValueChange = { content = it },
label = { Text("摘要/笔记") },
modifier = Modifier.fillMaxWidth(),
maxLines = 5
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedButton(
onClick = { showDeleteConfirmDialog = true },
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
modifier = Modifier.weight(1f)
) {
Text("删除")
}
Button(
onClick = {
val newBookmark = bookmark.apply {
this.bookText = bookText
this.content = content
}
onSave(newBookmark)
},
modifier = Modifier.weight(1f)
) {
Text("保存")
}
}
Spacer(modifier = Modifier.height(16.dp))
}
}
if (showDeleteConfirmDialog) {
AlertDialog(
onDismissRequest = { showDeleteConfirmDialog = false },
title = { Text("确认删除") },
text = { Text("你确定要删除这条书签吗?") },
confirmButton = {
TextButton(
onClick = {
showDeleteConfirmDialog = false
onDelete(bookmark)
},
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error)
) {
Text("删除")
}
},
dismissButton = {
TextButton(
onClick = { showDeleteConfirmDialog = false }
) {
Text("取消")
}
}
)
}
}
@@ -2,66 +2,165 @@ package io.legado.app.ui.book.bookmark
import android.app.Application
import android.net.Uri
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppLog
import io.legado.app.data.appDb
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import io.legado.app.data.dao.BookmarkDao
import io.legado.app.data.entities.Bookmark
import io.legado.app.utils.FileDoc
import io.legado.app.utils.GSON
import io.legado.app.utils.createFileIfNotExist
import io.legado.app.utils.openOutputStream
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.writeToOutputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class AllBookmarkViewModel(application: Application) : BaseViewModel(application) {
class AllBookmarkViewModel(
application: Application,
private val bookmarkDao: BookmarkDao
) : AndroidViewModel(application) {
private val _searchQuery = MutableStateFlow("")
val searchQuery = _searchQuery.asStateFlow()
/**
* 导出书签
*/
fun exportBookmark(treeUri: Uri) {
execute {
val dateFormat = SimpleDateFormat("yyMMddHHmmss", Locale.getDefault())
val fileName = "bookmark-${dateFormat.format(Date())}.json"
val dirDoc = FileDoc.fromUri(treeUri, true)
dirDoc.createFileIfNotExist(fileName).openOutputStream().getOrThrow().use {
GSON.writeToOutputStream(it, appDb.bookmarkDao.all)
private val _collapsedGroups = MutableStateFlow<Set<String>>(emptySet())
val collapsedGroups = _collapsedGroups.asStateFlow()
private val refreshTrigger = MutableSharedFlow<Unit>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) // FlowPreview 用于 debounce
val bookmarksState: StateFlow<Map<String, List<Bookmark>>> = combine(
refreshTrigger.onStart { emit(Unit) },
_searchQuery
) { _, query -> query }
.debounce(300L)
.flatMapLatest { query ->
if (query.isBlank()) {
bookmarkDao.flowAll()
} else {
bookmarkDao.flowSearchAll(query)
}
}.onError {
AppLog.put("导出失败\n${it.localizedMessage}", it, true)
}.onSuccess {
context.toastOnUi("导出成功")
}
.map { list ->
list.groupBy { "${it.bookName}(${it.bookAuthor})" }
}
.catch { e -> e.printStackTrace() }
.flowOn(Dispatchers.IO)
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
emptyMap()
)
private fun refreshBookmarks() {
viewModelScope.launch {
refreshTrigger.emit(Unit)
}
}
fun toggleGroupCollapse(groupKey: String) {
val current = _collapsedGroups.value
if (current.contains(groupKey)) {
_collapsedGroups.value = current - groupKey
} else {
_collapsedGroups.value = current + groupKey // 折叠
}
}
fun exportBookmarkMd(treeUri: Uri) {
execute {
val dateFormat = SimpleDateFormat("yyMMddHHmmss", Locale.getDefault())
val fileName = "bookmark-${dateFormat.format(Date())}.md"
val dirDoc = FileDoc.fromUri(treeUri, true)
val fileDoc = dirDoc.createFileIfNotExist(fileName).openOutputStream().getOrThrow()
fileDoc.use { outputStream ->
var name = ""
var author = ""
appDb.bookmarkDao.all.forEach {
if (it.bookName != name && it.bookAuthor != author) {
name = it.bookName
author = it.bookAuthor
outputStream.write("## ${it.bookName} ${it.bookAuthor}\n\n".toByteArray())
fun toggleAllCollapse(currentKeys: Set<String>) {
val currentCollapsed = _collapsedGroups.value
if (currentCollapsed.containsAll(currentKeys) && currentKeys.isNotEmpty()) {
_collapsedGroups.value = emptySet()
} else {
_collapsedGroups.value = currentKeys
}
}
fun onSearchQueryChanged(query: String) {
_searchQuery.value = query
}
fun updateBookmark(bookmark: Bookmark) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
bookmarkDao.insert(bookmark)
}
refreshBookmarks()
}
}
fun deleteBookmark(bookmark: Bookmark) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
bookmarkDao.delete(bookmark)
}
refreshBookmarks()
}
}
fun exportBookmark(treeUri: Uri, isMarkdown: Boolean) {
viewModelScope.launch(Dispatchers.IO) {
try {
val context = getApplication<Application>()
val dateFormat = SimpleDateFormat("yyMMddHHmmss", Locale.getDefault())
val suffix = if (isMarkdown) "md" else "json"
val fileName = "bookmark-${dateFormat.format(Date())}.$suffix"
val dirDoc = FileDoc.fromUri(treeUri, true)
val fileDoc = dirDoc.createFileIfNotExist(fileName)
fileDoc.openOutputStream().getOrThrow().use { outputStream ->
if (isMarkdown) {
writeMarkdown(outputStream, bookmarkDao.all)
} else {
GSON.writeToOutputStream(outputStream, bookmarkDao.all)
}
outputStream.write("#### ${it.chapterName}\n\n".toByteArray())
outputStream.write("###### 原文\n ${it.bookText}\n\n".toByteArray())
outputStream.write("###### 摘要\n ${it.content}\n\n".toByteArray())
}
withContext(Dispatchers.Main) {
context.toastOnUi("导出成功")
}
} catch (e: Exception) {
e.printStackTrace()
}
}.onError {
AppLog.put("导出失败\n${it.localizedMessage}", it, true)
}.onSuccess {
context.toastOnUi("导出成功")
}
}
private fun writeMarkdown(outputStream: java.io.OutputStream, bookmarks: List<Bookmark>) {
var name = ""
var author = ""
bookmarks.forEach {
if (it.bookName != name && it.bookAuthor != author) {
name = it.bookName
author = it.bookAuthor
outputStream.write("## ${it.bookName} ${it.bookAuthor}\n\n".toByteArray())
}
outputStream.write("#### ${it.chapterName}\n\n".toByteArray())
outputStream.write("###### 原文\n ${it.bookText}\n\n".toByteArray())
outputStream.write("###### 摘要\n ${it.content}\n\n".toByteArray())
}
}
}
@@ -1,6 +1,5 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -16,7 +15,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@@ -25,7 +23,8 @@ import androidx.compose.ui.unit.dp
@Composable
fun SearchBarSection(
query: String,
onQueryChange: (String) -> Unit
onQueryChange: (String) -> Unit,
placeholder: String = "搜索书名"
) {
Surface(
modifier = Modifier
@@ -38,14 +37,12 @@ fun SearchBarSection(
TextField(
value = query,
onValueChange = onQueryChange,
placeholder = { Text("搜索书名…") },
placeholder = { Text(placeholder) },
leadingIcon = { Icon(Icons.Default.Search, null) },
trailingIcon = {
Row(verticalAlignment = Alignment.CenterVertically) {
if (query.isNotEmpty()) {
IconButton(onClick = { onQueryChange("") }) {
Icon(Icons.Default.Clear, contentDescription = null)
}
if (query.isNotEmpty()) {
IconButton(onClick = { onQueryChange("") }) {
Icon(Icons.Default.Clear, null)
}
}
},
@@ -59,4 +56,4 @@ fun SearchBarSection(
)
)
}
}
}