新增发现页
新增发现页,展示所有动态的缩略图,点击可跳转到对应动态详情页。
This commit is contained in:
@@ -43,6 +43,7 @@ import com.aiosman.riderpro.ui.login.LoginPage
|
|||||||
import com.aiosman.riderpro.ui.login.SignupScreen
|
import com.aiosman.riderpro.ui.login.SignupScreen
|
||||||
import com.aiosman.riderpro.ui.login.UserAuthScreen
|
import com.aiosman.riderpro.ui.login.UserAuthScreen
|
||||||
import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen
|
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.modification.EditModificationScreen
|
||||||
import com.aiosman.riderpro.ui.post.NewPostImageGridScreen
|
import com.aiosman.riderpro.ui.post.NewPostImageGridScreen
|
||||||
import com.aiosman.riderpro.ui.post.NewPostScreen
|
import com.aiosman.riderpro.ui.post.NewPostScreen
|
||||||
@@ -75,6 +76,7 @@ sealed class NavigationRoute(
|
|||||||
data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen")
|
data object ChangePasswordScreen : NavigationRoute("ChangePasswordScreen")
|
||||||
data object FavouritesScreen : NavigationRoute("FavouritesScreen")
|
data object FavouritesScreen : NavigationRoute("FavouritesScreen")
|
||||||
data object NewPostImageGrid : NavigationRoute("NewPostImageGrid")
|
data object NewPostImageGrid : NavigationRoute("NewPostImageGrid")
|
||||||
|
data object Search : NavigationRoute("Search")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -219,6 +221,13 @@ fun NavigationController(
|
|||||||
composable(route = NavigationRoute.NewPostImageGrid.route) {
|
composable(route = NavigationRoute.NewPostImageGrid.route) {
|
||||||
NewPostImageGridScreen()
|
NewPostImageGridScreen()
|
||||||
}
|
}
|
||||||
|
composable(route = NavigationRoute.Search.route) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalAnimatedContentScope provides this,
|
||||||
|
) {
|
||||||
|
SearchScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.message.NotificationsScreen
|
||||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentsList
|
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.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.search.SearchScreen
|
||||||
import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo
|
import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo
|
||||||
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
|
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
|
||||||
@@ -124,7 +125,7 @@ fun IndexScreen() {
|
|||||||
) { page ->
|
) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> Home()
|
0 -> Home()
|
||||||
1 -> SearchScreen()
|
1 -> DiscoverScreen()
|
||||||
2 -> Add()
|
2 -> Add()
|
||||||
3 -> Notifications()
|
3 -> Notifications()
|
||||||
4 -> Profile()
|
4 -> Profile()
|
||||||
|
|||||||
@@ -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 = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.asPaddingValues
|
|||||||
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.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
@@ -37,6 +38,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
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.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
@@ -68,19 +71,28 @@ fun SearchScreen() {
|
|||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||||
|
val navigationBarPaddings =
|
||||||
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
|
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(Color.White)
|
.background(Color.White)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.padding(bottom = navigationBarPaddings)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(statusBarPaddingValues.calculateTopPadding()))
|
Spacer(modifier = Modifier.height(statusBarPaddingValues.calculateTopPadding()))
|
||||||
SearchInput(
|
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,
|
text = model.searchText,
|
||||||
onTextChange = {
|
onTextChange = {
|
||||||
model.searchText = it
|
model.searchText = it
|
||||||
@@ -89,7 +101,8 @@ fun SearchScreen() {
|
|||||||
model.search()
|
model.search()
|
||||||
// hide ime
|
// hide ime
|
||||||
keyboardController?.hide() // Hide the keyboard
|
keyboardController?.hide() // Hide the keyboard
|
||||||
}
|
},
|
||||||
|
focusRequester = focusRequester
|
||||||
)
|
)
|
||||||
if (model.showResult) {
|
if (model.showResult) {
|
||||||
Spacer(modifier = Modifier.padding(8.dp))
|
Spacer(modifier = Modifier.padding(8.dp))
|
||||||
@@ -115,8 +128,6 @@ fun SearchScreen() {
|
|||||||
pagerState = pagerState
|
pagerState = pagerState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +136,8 @@ fun SearchInput(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
text: String = "",
|
text: String = "",
|
||||||
onTextChange: (String) -> Unit = {},
|
onTextChange: (String) -> Unit = {},
|
||||||
onSearch: () -> Unit = {}
|
onSearch: () -> Unit = {},
|
||||||
|
focusRequester: FocusRequester = remember { FocusRequester() }
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -157,7 +169,8 @@ fun SearchInput(
|
|||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp)
|
.padding(start = 8.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.focusRequester(focusRequester),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
@@ -238,7 +251,10 @@ fun UserItem(accountProfile: AccountProfileEntity) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(16.dp).noRippleClickable {
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.noRippleClickable {
|
||||||
navController.navigate("AccountProfile/${accountProfile.id}")
|
navController.navigate("AccountProfile/${accountProfile.id}")
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
|||||||
5
app/src/main/res/drawable/rider_pro_images.xml
Normal file
5
app/src/main/res/drawable/rider_pro_images.xml
Normal 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>
|
||||||
Reference in New Issue
Block a user