feat: search

This commit is contained in:
AnranYus
2024-06-15 02:52:23 +08:00
parent 6b71b31dfc
commit 5baa502363
3 changed files with 142 additions and 54 deletions
@@ -32,36 +32,29 @@ import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridS
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.lsp.view.bean.Post
import com.lsp.view.ui.compose.theme.LspViewTheme
@@ -75,6 +68,7 @@ import coil.compose.SubcomposeAsyncImage
import coil.compose.SubcomposeAsyncImageContent
import com.lsp.view.R
import com.lsp.view.service.DownloadService
import com.lsp.view.ui.compose.widget.SearchBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -100,15 +94,14 @@ class PostActivity : ComponentActivity() {
setContent {
LspViewTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
) {
App()
}
}
}
viewModel.downloadAction.observe(this){
viewModel.downloadAction.observe(this) {
CoroutineScope(Dispatchers.IO).launch {
val result =
downloadBinder.downloadImage(viewModel.uiState.value.selectPost?.fileUrl ?: "")
@@ -116,15 +109,11 @@ class PostActivity : ComponentActivity() {
launch(Dispatchers.Main) {
if (result.isSuccess) {
Toast.makeText(
this@PostActivity,
"Download successful",
Toast.LENGTH_SHORT
this@PostActivity, "Download successful", Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
this@PostActivity,
"Download fail",
Toast.LENGTH_SHORT
this@PostActivity, "Download fail", Toast.LENGTH_SHORT
).show()
}
}
@@ -166,54 +155,61 @@ fun App(viewModel: PostViewModel = viewModel()) {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostListScreen(
navController: NavController,
onNavigateToDetail: (Post) -> Unit,
viewModel: PostViewModel
navController: NavController, onNavigateToDetail: (Post) -> Unit, viewModel: PostViewModel
) {
val postList by viewModel.postData.collectAsState()
val listState = rememberLazyStaggeredGridState()
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo }
.map { layoutInfo ->
snapshotFlow { listState.layoutInfo }.map { layoutInfo ->
layoutInfo.visibleItemsInfo.lastOrNull()?.index == postList.lastIndex
}
.distinctUntilChanged()
.collect { reachedEnd ->
}.distinctUntilChanged().collect { reachedEnd ->
//避免初次加载数据时,list未绘制导致被判断为到达底部
if (reachedEnd && listState.firstVisibleItemIndex != 0) {
viewModel.fetchPost()
}
}
}
LazyVerticalStaggeredGrid(
state = listState,
columns = StaggeredGridCells.Adaptive(200.dp),
verticalItemSpacing = 4.dp,
horizontalArrangement = Arrangement.spacedBy(4.dp),
content = {
items(items = postList, key = {
it.postId//提供key以保持列表顺序稳定
}) {
Row(Modifier.animateItemPlacement(tween(durationMillis = 250))) {
PostItem(
it,
clickable = {
onNavigateToDetail.invoke(it)
}
)
}
val uiState by viewModel.uiState.collectAsState()
Box(modifier = Modifier.background(MaterialTheme.colorScheme.secondaryContainer)) {
Column {
Row(modifier = Modifier.padding(vertical = 4.dp)) {
SearchBar(uiState.searchTarget) {
viewModel.fetchPost(it, refresh = true)
}
}
Row {
LazyVerticalStaggeredGrid(
state = listState,
columns = StaggeredGridCells.Adaptive(200.dp),
verticalItemSpacing = 4.dp,
horizontalArrangement = Arrangement.spacedBy(4.dp),
content = {
items(items = postList, key = {
it.postId//提供key以保持列表顺序稳定
}) {
Row(Modifier.animateItemPlacement(tween(durationMillis = 250))) {
PostItem(it, clickable = {
onNavigateToDetail.invoke(it)
})
}
}
},
modifier = Modifier.background(Color.Transparent)
)
}
}
)
}
}
@Composable
fun PostItem(post: Post, clickable: (Post) -> Unit) {
SubcomposeAsyncImage(
model = post.sampleUrl,
SubcomposeAsyncImage(model = post.sampleUrl,
contentScale = ContentScale.FillWidth,
contentDescription = null,
loading = {
@@ -236,8 +232,7 @@ fun PostItem(post: Post, clickable: (Post) -> Unit) {
.clip(RoundedCornerShape(5.dp))
.fillMaxWidth()
.wrapContentHeight()
.clickable { clickable.invoke(post) }
)
.clickable { clickable.invoke(post) })
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -251,7 +246,11 @@ fun DetailScreen(navController: NavController, viewModel: PostViewModel) {
scale *= zoomChange
rotation += rotationChange
}
Box(modifier = Modifier.background(Color.Black).fillMaxSize()){
Box(
modifier = Modifier
.background(Color.Black)
.fillMaxSize()
) {
AsyncImage(
model = uiState.selectPost?.sampleUrl,
contentDescription = null,
@@ -280,8 +279,11 @@ fun DetailScreen(navController: NavController, viewModel: PostViewModel) {
}
},
actions = {
IconButton(onClick = { viewModel.actionDownload()}) {
Icon(painter = painterResource(id = R.drawable.ic_twotone_arrow_downward_24), contentDescription = "Download")
IconButton(onClick = { viewModel.actionDownload() }) {
Icon(
painter = painterResource(id = R.drawable.ic_twotone_arrow_downward_24),
contentDescription = "Download"
)
}
},
)
@@ -32,21 +32,24 @@ class PostViewModel:ViewModel() {
downloadAction.postValue(Unit)
}
fun fetchPost(){
fun fetchPost(searchTarget: String = _uiState.value.searchTarget,refresh:Boolean = false){
viewModelScope.launch(Dispatchers.IO) {
_uiState.value.refresh = true
_uiState.value.searchTarget = searchTarget
try {
val data = repository.fetchPostData(
_uiState.value.searchTarget,
searchTarget,
_uiState.value.safeModel,
_uiState.value.page
)
_uiState.value.page ++
postData.value += data
postData.value = if (refresh){
data
}else{
postData.value + data
}
} catch (e: NetworkErrorException) {
//todo network error
}
_uiState.value.refresh = false
}
}
@@ -0,0 +1,83 @@
package com.lsp.view.ui.compose.widget
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun SearchBar(searchTarget: String = "", searchEvent: (String) -> Unit) {
Row(
modifier = Modifier
.padding(horizontal = 24.dp)
.fillMaxWidth()
.height(56.dp)
.background(Color.White, shape = RoundedCornerShape(56.dp)),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = null,
modifier = Modifier
.padding(15.dp)
.height(48.dp)
.width(48.dp)
)
var input: String by remember { mutableStateOf(searchTarget) }
val keyboardController = LocalSoftwareKeyboardController.current
val forceManager = LocalFocusManager.current
BasicTextField(
value = input,
onValueChange = {
input = it
},
singleLine = true,
textStyle = TextStyle(fontSize = 20.sp),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions {
keyboardController?.hide()
forceManager.clearFocus()
searchEvent.invoke(input)
},
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
)
Icon(
imageVector = Icons.Default.Search,
contentDescription = null,
modifier = Modifier
.padding(15.dp)
.height(48.dp)
.width(48.dp)
)
}
}