新增发现页

新增发现页,展示所有动态的缩略图,点击可跳转到对应动态详情页。
This commit is contained in:
2024-08-28 23:38:29 +08:00
parent a157b5ec11
commit 9c7c11f989
6 changed files with 267 additions and 11 deletions

View File

@@ -43,6 +43,7 @@ import com.aiosman.riderpro.ui.login.LoginPage
import com.aiosman.riderpro.ui.login.SignupScreen
import com.aiosman.riderpro.ui.login.UserAuthScreen
import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen
import com.aiosman.riderpro.ui.index.tabs.search.SearchScreen
import com.aiosman.riderpro.ui.modification.EditModificationScreen
import com.aiosman.riderpro.ui.post.NewPostImageGridScreen
import com.aiosman.riderpro.ui.post.NewPostScreen
@@ -75,6 +76,7 @@ sealed class NavigationRoute(
data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen")
data object FavouritesScreen : NavigationRoute("FavouritesScreen")
data object NewPostImageGrid : NavigationRoute("NewPostImageGrid")
data object Search : NavigationRoute("Search")
}
@@ -219,6 +221,13 @@ fun NavigationController(
composable(route = NavigationRoute.NewPostImageGrid.route) {
NewPostImageGridScreen()
}
composable(route = NavigationRoute.Search.route) {
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
SearchScreen()
}
}
}

View File

@@ -38,6 +38,7 @@ import com.aiosman.riderpro.ui.index.tabs.add.AddPage
import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen
import com.aiosman.riderpro.ui.index.tabs.moment.MomentsList
import com.aiosman.riderpro.ui.index.tabs.profile.ProfilePage
import com.aiosman.riderpro.ui.index.tabs.search.DiscoverScreen
import com.aiosman.riderpro.ui.index.tabs.search.SearchScreen
import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
@@ -124,7 +125,7 @@ fun IndexScreen() {
) { page ->
when (page) {
0 -> Home()
1 -> SearchScreen()
1 -> DiscoverScreen()
2 -> Add()
3 -> Notifications()
4 -> Profile()

View File

@@ -0,0 +1,187 @@
package com.aiosman.riderpro.ui.index.tabs.search
import android.graphics.drawable.VectorDrawable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
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.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.PostViewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
fun DiscoverScreen() {
val model = DiscoverViewModel
val navController = LocalNavController.current
val coroutineScope = rememberCoroutineScope()
val systemUiController = rememberSystemUiController()
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
}
Column(
modifier = Modifier
.background(Color.White)
.fillMaxSize()
.padding(bottom = navigationBarPaddings)
) {
Spacer(modifier = Modifier.height(statusBarPaddingValues.calculateTopPadding()))
SearchButton(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, start = 24.dp, end = 24.dp),
) {
navController.navigate("Search")
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.weight(1f)
) {
DiscoverView()
}
}
}
@Composable
fun SearchButton(
modifier: Modifier = Modifier,
clickAction: () -> Unit = {}
) {
Box(
modifier = modifier
.clip(shape = RoundedCornerShape(8.dp))
.background(Color(0xFFEEEEEE))
.padding(horizontal = 16.dp, vertical = 8.dp)
.noRippleClickable {
clickAction()
}
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Search,
contentDescription = null
)
Box {
Text(
text = "Search",
modifier = Modifier.padding(start = 8.dp),
color = Color(0xFF9E9E9E),
fontSize = 18.sp
)
}
}
}
}
@Composable
fun DiscoverView() {
val model = DiscoverViewModel
var dataFlow = model.discoverMomentsFlow
var moments = dataFlow.collectAsLazyPagingItems()
val context = LocalContext.current
val navController = LocalNavController.current
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize()
) {
items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.padding(2.dp)
.noRippleClickable {
PostViewModel.preTransit(momentItem)
navController.navigate(
NavigationRoute.Post.route.replace(
"{id}",
momentItem.id.toString()
)
)
}
) {
CustomAsyncImage(
imageUrl = momentItem.images[0].thumbnail,
contentDescription = "",
modifier = Modifier
.fillMaxSize(),
context = context
)
if (momentItem.images.size > 1) {
Box(
modifier = Modifier
.padding(top = 8.dp, end = 8.dp)
.align(Alignment.TopEnd)
) {
Image(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.rider_pro_images),
contentDescription = "",
)
}
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
package com.aiosman.riderpro.ui.index.tabs.search
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.entity.MomentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object DiscoverViewModel:ViewModel() {
private val momentService: MomentService = MomentServiceImpl()
private val _discoverMomentsFlow =
MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val discoverMomentsFlow = _discoverMomentsFlow.asStateFlow()
init {
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
MomentPagingSource(
MomentRemoteDataSource(momentService),
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_discoverMomentsFlow.value = it
}
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
@@ -37,6 +38,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -68,19 +71,28 @@ fun SearchScreen() {
val keyboardController = LocalSoftwareKeyboardController.current
val systemUiController = rememberSystemUiController()
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Column(
modifier = Modifier
.background(Color.White)
.fillMaxSize()
.padding(bottom = navigationBarPaddings)
) {
Spacer(modifier = Modifier.height(statusBarPaddingValues.calculateTopPadding()))
SearchInput(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp, start = 24.dp, end = 24.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, start = 24.dp, end = 24.dp),
text = model.searchText,
onTextChange = {
model.searchText = it
@@ -89,7 +101,8 @@ fun SearchScreen() {
model.search()
// hide ime
keyboardController?.hide() // Hide the keyboard
}
},
focusRequester = focusRequester
)
if (model.showResult) {
Spacer(modifier = Modifier.padding(8.dp))
@@ -115,8 +128,6 @@ fun SearchScreen() {
pagerState = pagerState
)
}
}
}
@@ -125,7 +136,8 @@ fun SearchInput(
modifier: Modifier = Modifier,
text: String = "",
onTextChange: (String) -> Unit = {},
onSearch: () -> Unit = {}
onSearch: () -> Unit = {},
focusRequester: FocusRequester = remember { FocusRequester() }
) {
Box(
modifier = modifier
@@ -157,7 +169,8 @@ fun SearchInput(
},
modifier = Modifier
.padding(start = 8.dp)
.fillMaxWidth(),
.fillMaxWidth()
.focusRequester(focusRequester),
singleLine = true,
textStyle = TextStyle(
fontSize = 18.sp
@@ -238,7 +251,10 @@ fun UserItem(accountProfile: AccountProfileEntity) {
val context = LocalContext.current
val navController = LocalNavController.current
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp).noRippleClickable {
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.noRippleClickable {
navController.navigate("AccountProfile/${accountProfile.id}")
},
verticalAlignment = Alignment.CenterVertically

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11.4,12.53l1.63,2.18 2.58,-3.22c0.2,-0.25 0.58,-0.25 0.78,0l2.96,3.7c0.26,0.33 0.03,0.81 -0.39,0.81L9,16c-0.41,0 -0.65,-0.47 -0.4,-0.8l2,-2.67c0.2,-0.26 0.6,-0.26 0.8,0zM2,7v13c0,1.1 0.9,2 2,2h13c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L5,20c-0.55,0 -1,-0.45 -1,-1L4,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1z"/>
</vector>