更改目录结构

This commit is contained in:
2024-07-23 15:25:00 +08:00
parent 82f58d4b9c
commit dfbc151e4e
47 changed files with 1007 additions and 533 deletions

View File

@@ -0,0 +1,156 @@
package com.aiosman.riderpro.ui
import ModificationListScreen
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.ui.comment.CommentsScreen
import com.aiosman.riderpro.ui.follower.FollowerScreen
import com.aiosman.riderpro.ui.gallery.OfficialGalleryScreen
import com.aiosman.riderpro.ui.gallery.OfficialPhotographerScreen
import com.aiosman.riderpro.ui.gallery.ProfileTimelineScreen
import com.aiosman.riderpro.ui.index.IndexScreen
import com.aiosman.riderpro.ui.like.LikeScreen
import com.aiosman.riderpro.ui.location.LocationDetailScreen
import com.aiosman.riderpro.ui.message.NotificationsScreen
import com.aiosman.riderpro.ui.modification.EditModificationScreen
import com.aiosman.riderpro.ui.post.NewPostScreen
import com.aiosman.riderpro.ui.post.PostScreen
sealed class NavigationRoute(
val route: String,
) {
data object Index : NavigationRoute("Index")
data object ProfileTimeline : NavigationRoute("ProfileTimeline")
data object LocationDetail : NavigationRoute("LocationDetail/{x}/{y}")
data object OfficialPhoto : NavigationRoute("OfficialPhoto")
data object OfficialPhotographer : NavigationRoute("OfficialPhotographer")
data object Post : NavigationRoute("Post/{id}")
data object ModificationList : NavigationRoute("ModificationList")
data object MyMessage : NavigationRoute("MyMessage")
data object Comments : NavigationRoute("Comments")
data object Likes : NavigationRoute("Likes")
data object Followers : NavigationRoute("Followers")
data object NewPost : NavigationRoute("NewPost")
data object EditModification : NavigationRoute("EditModification")
}
@Composable
fun NavigationController(navController: NavHostController) {
val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp()
}
NavHost(
navController = navController,
startDestination = NavigationRoute.Index.route,
) {
composable(route = NavigationRoute.Index.route) {
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
IndexScreen()
}
}
composable(route = NavigationRoute.ProfileTimeline.route) {
ProfileTimelineScreen()
}
composable(
route = NavigationRoute.LocationDetail.route,
arguments = listOf(
navArgument("x") { type = NavType.FloatType },
navArgument("y") { type = NavType.FloatType }
)
) {
Box(
modifier = Modifier.padding(bottom = navigationBarHeight)
) {
val x = it.arguments?.getFloat("x") ?: 0f
val y = it.arguments?.getFloat("y") ?: 0f
LocationDetailScreen(
x, y
)
}
}
composable(route = NavigationRoute.OfficialPhoto.route) {
OfficialGalleryScreen()
}
composable(route = NavigationRoute.OfficialPhotographer.route) {
OfficialPhotographerScreen()
}
composable(
route = NavigationRoute.Post.route,
arguments = listOf(navArgument("id") { type = NavType.StringType })
) { backStackEntry ->
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
val id = backStackEntry.arguments?.getString("id")
PostScreen(
id!!
)
}
}
composable(route = NavigationRoute.ModificationList.route) {
ModificationListScreen()
}
composable(route = NavigationRoute.MyMessage.route) {
NotificationsScreen()
}
composable(route = NavigationRoute.Comments.route) {
CommentsScreen()
}
composable(route = NavigationRoute.Likes.route) {
LikeScreen()
}
composable(route = NavigationRoute.Followers.route) {
FollowerScreen()
}
composable(route = NavigationRoute.NewPost.route) {
NewPostScreen()
}
composable(route = NavigationRoute.EditModification.route) {
Box(
modifier = Modifier.padding(top = 64.dp)
) {
EditModificationScreen()
}
}
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun Navigation() {
val navController = rememberNavController()
SharedTransitionLayout {
CompositionLocalProvider(
LocalNavController provides navController,
LocalSharedTransitionScope provides this@SharedTransitionLayout,
) {
Box {
NavigationController(navController = navController)
}
}
}
}

View File

@@ -0,0 +1,139 @@
package com.aiosman.riderpro.ui.comment
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.ui.post.CommentsSection
import com.aiosman.riderpro.R
@Preview
@Composable
fun CommentModalContent(onDismiss: () -> Unit = {}) {
val insets = WindowInsets
val imePadding = insets.ime.getBottom(density = LocalDensity.current)
var bottomPadding by remember { mutableStateOf(0.dp) }
LaunchedEffect(imePadding) {
bottomPadding = imePadding.dp
}
DisposableEffect(Unit) {
onDispose {
onDismiss()
}
}
Column(
modifier = Modifier
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, bottom = 16.dp, end = 16.dp)
) {
Text(
"评论",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center)
)
}
HorizontalDivider(
color = Color(0xFFF7F7F7)
)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.weight(1f)
) {
CommentsSection{}
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color(0xfff7f7f7))
.padding(horizontal = 16.dp)
.height(64.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically
) {
// rounded
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.clip(RoundedCornerShape(20.dp))
.background(Color(0xffe5e5e5))
.padding(horizontal = 16.dp, vertical = 12.dp)
) {
BasicTextField(
value = "",
onValueChange = { },
modifier = Modifier
.fillMaxWidth(),
textStyle = TextStyle(
color = Color.Black,
fontWeight = FontWeight.Normal
)
)
}
Spacer(modifier = Modifier.width(16.dp))
Image(
painter = painterResource(id = R.drawable.rider_pro_send),
contentDescription = "Send",
modifier = Modifier
.size(32.dp)
)
}
}
}
}

View File

@@ -0,0 +1,155 @@
package com.aiosman.riderpro.ui.comment
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
@Preview
@Composable
fun CommentsScreen() {
StatusBarMaskLayout(
darkIcons = true,
maskBoxBackgroundColor = Color(0xFFFFFFFF)
) {
Column(
modifier = Modifier
.weight(1f)
.background(color = Color(0xFFFFFFFF))
.padding(horizontal = 16.dp)
) {
NoticeScreenHeader("COMMENTS")
Spacer(modifier = Modifier.height(28.dp))
LazyColumn(
modifier = Modifier.weight(1f)
) {
item {
repeat(20) {
CommentsItem()
}
BottomNavigationPlaceholder()
}
}
}
}
}
@Composable
fun NoticeScreenHeader(
title:String
) {
val nav = LocalNavController.current
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_back),
contentDescription = title,
modifier = Modifier.size(16.dp).clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
nav.popBackStack()
}
)
Spacer(modifier = Modifier.size(12.dp))
Text(title, fontWeight = FontWeight.Bold, fontSize = 17.sp)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "More",
modifier = Modifier.size(24.dp)
)
}
}
@Composable
fun CommentsItem() {
Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Avatar",
modifier = Modifier
.size(40.dp)
)
Spacer(modifier = Modifier.size(12.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text("Username", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.size(4.dp))
Text("Content", color = Color(0x99000000), fontSize = 12.sp)
Spacer(modifier = Modifier.size(4.dp))
Text("Date", color = Color(0x99000000), fontSize = 12.sp)
Spacer(modifier = Modifier.height(16.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_like),
contentDescription = "Like",
modifier = Modifier.size(16.dp),
)
Text(
"270",
color = Color(0x99000000),
fontSize = 12.sp,
modifier = Modifier.padding(start = 4.dp)
)
Spacer(modifier = Modifier.width(45.dp))
Image(
painter = painterResource(id = R.drawable.rider_pro_comments),
contentDescription = "Comments",
modifier = Modifier.size(16.dp)
)
Text(
"270",
color = Color(0x99000000),
fontSize = 12.sp,
modifier = Modifier.padding(start = 4.dp)
)
}
}
Box {
Image(
painter = painterResource(id = R.drawable.default_moment_img),
contentDescription = "More",
modifier = Modifier.size(64.dp)
)
}
}
}
}

View File

@@ -0,0 +1,41 @@
package com.aiosman.riderpro.ui.composables
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.animation.with
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedCounter(count: Int, modifier: Modifier = Modifier, fontSize: Int = 24) {
AnimatedContent(
targetState = count,
transitionSpec = {
// Compare the incoming number with the previous number.
if (targetState > initialState) {
// If the target number is larger, it slides up and fades in
// while the initial (smaller) number slides up and fades out.
(slideInVertically { height -> height } + fadeIn()).togetherWith(slideOutVertically { height -> -height } + fadeOut())
} else {
// If the target number is smaller, it slides down and fades in
// while the initial number slides down and fades out.
(slideInVertically { height -> -height } + fadeIn()).togetherWith(slideOutVertically { height -> height } + fadeOut())
}.using(
// Disable clipping since the faded slide-in/out should
// be displayed out of bounds.
SizeTransform(clip = false)
)
}
) { targetCount ->
Text(text = "$targetCount", modifier = modifier, fontSize = fontSize.sp)
}
}

View File

@@ -0,0 +1,71 @@
package com.aiosman.riderpro.ui.composables
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource
import com.aiosman.riderpro.R
import kotlinx.coroutines.launch
@Composable
fun AnimatedLikeIcon(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null
) {
var liked by remember { mutableStateOf(false) }
val animatableRotation = remember { Animatable(0f) }
val animatedColor by animateColorAsState(targetValue = if (liked) Color(0xFFd83737) else Color.Black)
val scope = rememberCoroutineScope()
suspend fun shake() {
repeat(2) {
animatableRotation.animateTo(
targetValue = 10f,
animationSpec = tween(100)
) {
}
animatableRotation.animateTo(
targetValue = -10f,
animationSpec = tween(100)
) {
}
}
animatableRotation.animateTo(
targetValue = 0f,
animationSpec = tween(100)
)
}
Box(contentAlignment = Alignment.Center, modifier = Modifier.clickable {
liked = !liked
onClick?.invoke()
// Trigger shake animation
scope.launch {
shake()
}
}) {
Image(
painter = painterResource(id = R.drawable.rider_pro_like),
contentDescription = "Like",
modifier = modifier.graphicsLayer {
rotationZ = animatableRotation.value
},
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(animatedColor)
)
}
}

View File

@@ -0,0 +1,24 @@
package com.aiosman.riderpro.ui.composables
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
@Composable
fun BottomNavigationPlaceholder(
color: Color? = null
) {
val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp()
}
Box(
modifier = Modifier.height(navigationBarHeight).fillMaxWidth().background(color ?: Color.Transparent)
)
}

View File

@@ -0,0 +1,67 @@
package com.aiosman.riderpro.ui.composables
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
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.systemBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun StatusBarMask(darkIcons: Boolean = true) {
val paddingValues = WindowInsets.systemBars.asPaddingValues()
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = darkIcons)
}
Spacer(modifier = Modifier.height(paddingValues.calculateTopPadding()))
}
@Composable
fun StatusBarMaskLayout(
modifier: Modifier = Modifier,
darkIcons: Boolean = true,
maskBoxBackgroundColor: Color = Color.Transparent,
content: @Composable ColumnScope.() -> Unit
) {
val paddingValues = WindowInsets.systemBars.asPaddingValues()
val systemUiController = rememberSystemUiController()
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = darkIcons)
}
Column(
modifier = modifier.fillMaxSize()
) {
Box(
modifier = Modifier
.height(paddingValues.calculateTopPadding())
.fillMaxWidth()
.background(maskBoxBackgroundColor)
) {
}
content()
if (navigationBarPaddings > 24.dp) {
Box(
modifier = Modifier
.height(navigationBarPaddings).fillMaxWidth().background(Color.White)
)
}
}
}

View File

@@ -0,0 +1,91 @@
package com.aiosman.riderpro.ui.follower
import androidx.compose.foundation.Image
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
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.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
@Preview
@Composable
fun FollowerScreen() {
StatusBarMaskLayout(
modifier = Modifier.padding(horizontal = 16.dp)
) {
NoticeScreenHeader("FOLLOWERS")
Spacer(modifier = Modifier.height(28.dp))
LazyColumn(
modifier = Modifier.weight(1f)
) {
item {
repeat(20) {
FollowerItem()
}
}
}
}
}
@Composable
fun FollowerItem() {
Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Follower",
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text("Username", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(5.dp))
Text("Username", fontSize = 12.sp, color = Color(0x99000000))
}
Box {
Image(
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = "Like",
modifier = Modifier
.width(79.dp)
.height(24.dp)
)
Text(
"FOLLOW",
fontSize = 14.sp,
color = Color(0xFFFFFFFF),
modifier = Modifier.align(
Alignment.Center
)
)
}
}
}
}

View File

@@ -0,0 +1,278 @@
package com.aiosman.riderpro.ui.gallery
import androidx.compose.foundation.Canvas
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.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
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.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.R
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun ProfileTimelineScreen() {
val pagerState = rememberPagerState(pageCount = { 2 })
val scope = rememberCoroutineScope()
val systemUiController = rememberSystemUiController()
fun switchToPage(page: Int) {
scope.launch {
pagerState.animateScrollToPage(page)
}
}
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
}
Scaffold(
topBar = {
TopAppBar(
title = {
Text("Gallery")
},
navigationIcon = { },
actions = { })
},
) { paddingValues: PaddingValues ->
Box(modifier = Modifier.padding(paddingValues)) {
Column(modifier = Modifier) {
ScrollableTabRow(
edgePadding = 0.dp,
selectedTabIndex = pagerState.currentPage,
modifier = Modifier,
divider = { },
indicator = { tabPositions ->
Box(
modifier = Modifier
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
) {
Box(
modifier = Modifier
.align(Alignment.Center)
.height(4.dp)
.width(16.dp)
.background(color = Color.Red)
)
}
}
) {
Tab(
text = { Text("Timeline", color = Color.Black) },
selected = pagerState.currentPage == 0,
onClick = { switchToPage(0) }
)
Tab(
text = { Text("Position", color = Color.Black) },
selected = pagerState.currentPage == 1,
onClick = { switchToPage(1) }
)
}
HorizontalPager(
state = pagerState,
modifier = Modifier
.weight(1f)
.fillMaxSize()
) { page ->
when (page) {
0 -> GalleryTimeline()
1 -> GalleryPosition()
}
}
}
}
}
}
@Composable
fun GalleryTimeline() {
val mockList = listOf("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
Box(
modifier = Modifier
.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
) {
items(mockList) { item ->
TimelineItem()
}
}
}
}
@Composable
fun DashedVerticalLine(modifier: Modifier = Modifier) {
BoxWithConstraints(modifier = modifier) {
Canvas(modifier = Modifier.height(maxHeight)) {
val path = Path().apply {
moveTo(size.width / 2, 0f)
lineTo(size.width / 2, size.height)
}
drawPath(
path = path,
color = Color.Gray,
)
}
}
}
@Composable
fun DashedLine() {
Canvas(modifier = Modifier
.width(1.dp) // 控制线条的宽度
.fillMaxHeight()) { // 填满父容器的高度
val canvasWidth = size.width
val canvasHeight = size.height
// 创建一个PathEffect来定义如何绘制线段
val pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
drawLine(
color = Color.Gray, // 线条颜色
start = Offset(x = canvasWidth / 2, y = 0f), // 起始点
end = Offset(x = canvasWidth / 2, y = canvasHeight), // 终点
pathEffect = pathEffect // 应用虚线效果
)
}
}
@Preview
@Composable
fun TimelineItem() {
val itemsList = listOf("1", "2", "3", "4", "5", "6", "7", "8", "9")
Box(
modifier = Modifier
.padding(16.dp)
.fillMaxSize()
.wrapContentWidth()
) {
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Column(
modifier = Modifier
.width(64.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("12", fontSize = 22.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
Text("7月", fontSize = 20.sp,fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
// add vertical dash line
// Box(
// modifier = Modifier
// .height(120.dp)
// .width(3.dp)
// .background(Color.Gray)
// )
DashedLine()
}
Column {
Row(
modifier = Modifier
.padding(bottom = 16.dp)
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "",
modifier = Modifier
.padding(end = 16.dp)
.size(24.dp)
.clip(CircleShape) // Clip the image to a circle
)
Text("Onyama Limba")
}
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
) {
Column {
repeat(3) { // Create three rows
Row(modifier = Modifier.weight(1f)) {
repeat(3) { // Create three columns in each row
Box(
modifier = Modifier
.weight(1f)
.aspectRatio(1f) // Keep the aspect ratio 1:1 for square shape
.padding(4.dp)
){
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray)
) {
Text("1")
}
}
}
}
}
}
}
}
}
}
}
@Composable
fun GalleryPosition() {
val mockList = listOf("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
Box(
modifier = Modifier
.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
) {
items(mockList) { item ->
TimelineItem()
}
}
}
}

View File

@@ -0,0 +1,201 @@
package com.aiosman.riderpro.ui.gallery
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
@Preview
@Composable
fun OfficialGalleryScreen() {
StatusBarMaskLayout {
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, end = 16.dp)
) {
OfficialGalleryPageHeader()
Spacer(modifier = Modifier.height(16.dp))
ImageGrid()
}
}
}
@Composable
fun OfficialGalleryPageHeader() {
Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_nav_back), // Replace with your logo resource
contentDescription = "Logo",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "官方摄影师作品", fontWeight = FontWeight.Bold, fontSize = 16.sp)
}
}
@Composable
fun CertificationSection() {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFFFF3CD), RoundedCornerShape(8.dp))
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_foreground), // Replace with your certification icon resource
contentDescription = "Certification Icon",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "成为认证摄影师", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { /*TODO*/ },
) {
Text(text = "去认证", color = Color.White)
}
}
}
@Composable
fun ImageGrid() {
val photographers = listOf(
Pair(
"Diego Morata",
R.drawable.rider_pro_moment_demo_1
), // Replace with your image resources
Pair("Usha Oliver", R.drawable.rider_pro_moment_demo_2),
Pair("Mohsen Salehi", R.drawable.rider_pro_moment_demo_3),
Pair("Thanawan Chadee", R.drawable.rider_pro_moment_demo_1),
Pair("Photographer 5", R.drawable.rider_pro_moment_demo_2),
Pair("Photographer 6", R.drawable.rider_pro_moment_demo_3),
Pair(
"Diego Morata",
R.drawable.rider_pro_moment_demo_1
), // Replace with your image resources
Pair("Usha Oliver", R.drawable.rider_pro_moment_demo_2),
Pair("Mohsen Salehi", R.drawable.rider_pro_moment_demo_3),
Pair("Thanawan Chadee", R.drawable.rider_pro_moment_demo_1),
Pair("Photographer 5", R.drawable.rider_pro_moment_demo_2),
Pair("Photographer 6", R.drawable.rider_pro_moment_demo_3),
Pair(
"Diego Morata",
R.drawable.rider_pro_moment_demo_1
), // Replace with your image resources
Pair("Usha Oliver", R.drawable.rider_pro_moment_demo_2),
Pair("Mohsen Salehi", R.drawable.rider_pro_moment_demo_3),
Pair("Thanawan Chadee", R.drawable.rider_pro_moment_demo_1),
Pair("Photographer 5", R.drawable.rider_pro_moment_demo_2),
Pair("Photographer 6", R.drawable.rider_pro_moment_demo_3),
Pair(
"Diego Morata",
R.drawable.rider_pro_moment_demo_1
), // Replace with your image resources
Pair("Usha Oliver", R.drawable.rider_pro_moment_demo_2),
Pair("Mohsen Salehi", R.drawable.rider_pro_moment_demo_3),
Pair("Thanawan Chadee", R.drawable.rider_pro_moment_demo_1),
Pair("Photographer 5", R.drawable.rider_pro_moment_demo_2),
Pair("Photographer 6", R.drawable.rider_pro_moment_demo_3)
)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(photographers.size) { index ->
PhotographerCard(photographers[index].first, photographers[index].second)
}
item{
BottomNavigationPlaceholder()
}
}
}
@Composable
fun PhotographerCard(name: String, imageRes: Int) {
val navController = LocalNavController.current
Box(
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(8.dp))
.background(Color.LightGray)
) {
Image(
painter = painterResource(id = imageRes),
contentDescription = name,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(270.dp)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0x55000000))
.align(Alignment.BottomStart)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable {
navController.navigate("OfficialPhotographer")
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.default_avatar), // Replace with your profile picture resource
contentDescription = "Profile Picture",
modifier = Modifier
.size(24.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = name, color = Color.White)
}
}
}
}

View File

@@ -0,0 +1,294 @@
package com.aiosman.riderpro.ui.gallery
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
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.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
data class ArtWork(
val id: Int,
val resId: Int,
)
fun GenerateMockArtWorks(): List<ArtWork> {
val pickupImage = listOf(
R.drawable.default_avatar,
R.drawable.default_moment_img,
R.drawable.rider_pro_moment_demo_1,
R.drawable.rider_pro_moment_demo_2,
R.drawable.rider_pro_moment_demo_3,
)
return List(30) {
ArtWork(
id = it,
resId = pickupImage.random()
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Preview
@Composable
fun OfficialPhotographerScreen() {
val lazyListState = rememberLazyListState()
var artWorks by remember { mutableStateOf<List<ArtWork>>(emptyList()) }
LaunchedEffect(Unit) {
artWorks = GenerateMockArtWorks()
}
// Observe the scroll state and calculate opacity
val alpha by remember {
derivedStateOf {
// Example calculation: Adjust the range and formula as needed
val alp = minOf(1f, lazyListState.firstVisibleItemScrollOffset / 900f)
Log.d("alpha", "alpha: $alp")
alp
}
}
StatusBarMaskLayout(
maskBoxBackgroundColor = Color.Black,
darkIcons = false
) {
Column {
Box(
modifier = Modifier
.background(Color.Black)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState
) {
item {
Box(
modifier = Modifier
.height(400.dp)
.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.drawable.default_moment_img),
contentDescription = "Logo",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
// dark alpha overlay
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = alpha))
)
// on bottom of box
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 1f),
Color.Black.copy(alpha = 1f),
Color.Black.copy(alpha = 0f),
),
startY = Float.POSITIVE_INFINITY,
endY = 0f
)
)
.padding(16.dp)
.align(alignment = Alignment.BottomCenter)
) {
Column(
modifier = Modifier.fillMaxSize(),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "",
modifier = Modifier
.size(32.dp)
.clip(CircleShape) // Clip the image to a circle
)
Spacer(modifier = Modifier.width(8.dp))
// name
Text("Onyama Limba", color = Color.White, fontSize = 14.sp)
Spacer(modifier = Modifier.width(8.dp))
// round box
Box(
modifier = Modifier
.background(Color.Red, CircleShape)
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
// certification
Text("摄影师", color = Color.White, fontSize = 12.sp)
}
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = { /*TODO*/ },
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = null,
tint = Color.White
)
}
Spacer(modifier = Modifier.width(4.dp))
Text("123", color = Color.White)
Spacer(modifier = Modifier.width(8.dp))
IconButton(
onClick = {},
modifier = Modifier.size(32.dp)
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_eye),
contentDescription = "",
modifier = Modifier
.size(24.dp)
)
}
Spacer(modifier = Modifier.width(4.dp))
Text("123", color = Color.White)
}
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
// description
Text(
"摄影师 Diego Morata 的作品",
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
// circle avatar
}
}
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val imageSize =
(screenWidth - (4.dp * 4)) / 3 // Subtracting total padding and divi
val itemWidth = screenWidth / 3 - 4.dp * 2
FlowRow(
modifier = Modifier.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
maxItemsInEachRow = 3
) {
for (artWork in artWorks) {
Box(
modifier = Modifier
.width(itemWidth)
.aspectRatio(1f)
.background(Color.Gray)
) {
Image(
painter = painterResource(id = artWork.resId),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier
.width(imageSize)
.aspectRatio(1f)
)
}
}
BottomNavigationPlaceholder()
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.background(Color.Black.copy(alpha = alpha))
.padding(horizontal = 16.dp)
) {
Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_back),
colorFilter = ColorFilter.tint(Color.White),
contentDescription = "",
modifier = Modifier
.size(32.dp)
.clip(CircleShape) // Clip the image to a circle
)
if (alpha == 1f) {
Spacer(modifier = Modifier.width(8.dp))
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "",
modifier = Modifier
.size(32.dp)
.clip(CircleShape) // Clip the image to a circle
)
Spacer(modifier = Modifier.width(8.dp))
Text("Onyama Limba", color = Color.White)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,209 @@
package com.aiosman.riderpro.ui.index
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
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.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemColors
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.aiosman.riderpro.ui.index.tabs.add.AddPage
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.shorts.ShortVideo
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun IndexScreen() {
val model = IndexViewModel
val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp()
}
val item = listOf(
NavigationItem.Home,
NavigationItem.Street,
NavigationItem.Add,
NavigationItem.Message,
NavigationItem.Profile
)
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Black)
}
Scaffold(
bottomBar = {
NavigationBar(
modifier = Modifier.height(56.dp + navigationBarHeight),
containerColor = Color.Black
) {
item.forEachIndexed { idx, it ->
val isSelected = model.tabIndex == idx
val iconTint by animateColorAsState(
targetValue = if (isSelected) Color.Red else Color.White,
animationSpec = tween(durationMillis = 250), label = ""
)
NavigationBarItem(
selected = isSelected,
onClick = {
model.tabIndex = idx
// if (it.route == NavigationItem.Add.route || it.route == NavigationItem.Message.route) {
// systemUiController.setStatusBarColor(Color.Black, darkIcons = false)
// } else {
// systemUiController.setStatusBarColor(
// Color.Transparent,
// darkIcons = true
// )
// }
},
colors = NavigationBarItemColors(
selectedTextColor = Color.Red,
selectedIndicatorColor = Color.Black,
unselectedTextColor = Color.Red,
disabledIconColor = Color.Red,
disabledTextColor = Color.Red,
selectedIconColor = iconTint,
unselectedIconColor = iconTint,
),
icon = {
Icon(
modifier = Modifier.size(24.dp),
imageVector = if (isSelected) it.selectedIcon() else it.icon(),
contentDescription = null,
tint = iconTint
)
}
)
}
}
}
) { innerPadding ->
Box(
modifier = Modifier
) {
when (model.tabIndex) {
0 -> Box(
modifier = Modifier.padding(innerPadding)
) {
Home()
}
1 -> Street()
2 -> Box(
modifier = Modifier.padding(innerPadding)
) { Add() }
3 -> Box(
modifier = Modifier.padding(innerPadding)
) { Video() }
4 -> Box(
modifier = Modifier.padding(innerPadding)
) { Profile() }
}
}
}
}
@Composable
fun Home() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MomentsList()
}
}
@Composable
fun Street() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
StreetPage()
}
}
@Composable
fun Add() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Black, darkIcons = false)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AddPage()
}
}
@Composable
fun Video() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Black, darkIcons = false)
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
) {
ShortVideo()
}
}
@Composable
fun Profile() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = true)
}
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
ProfilePage()
}
}

View File

@@ -0,0 +1,10 @@
package com.aiosman.riderpro.ui.index
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
object IndexViewModel:ViewModel() {
var tabIndex by mutableStateOf(0)
}

View File

@@ -0,0 +1,37 @@
package com.aiosman.riderpro.ui.index
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import com.aiosman.riderpro.R
sealed class NavigationItem(
val route: String,
val icon: @Composable () -> ImageVector,
val selectedIcon: @Composable () -> ImageVector = icon
) {
data object Home : NavigationItem("Home",
icon = { ImageVector.vectorResource(R.drawable.rider_pro_home) },
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_home_filed) }
)
data object Street : NavigationItem("Street",
icon = { ImageVector.vectorResource(R.drawable.rider_pro_location) },
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_location_filed) }
)
data object Add : NavigationItem("Add",
icon = { ImageVector.vectorResource(R.drawable.rider_pro_moment_add) },
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_moment_add) }
)
data object Message : NavigationItem("Message",
icon = { ImageVector.vectorResource(R.drawable.rider_pro_video_outline) },
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_video) }
)
data object Profile : NavigationItem("Profile",
icon = { ImageVector.vectorResource(R.drawable.rider_pro_profile) },
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_profile_filed) }
)
}

View File

@@ -0,0 +1,53 @@
package com.aiosman.riderpro.ui.index.tabs.add
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
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.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.ui.post.NewPostViewModel
import com.aiosman.riderpro.R
@Composable
fun AddPage(){
val navController = LocalNavController.current
Column(modifier = Modifier
.fillMaxSize()
.background(Color.Black)) {
AddBtn(icon = R.drawable.rider_pro_icon_rider_share, text = "Rider Share") {
NewPostViewModel.asNewPost()
navController.navigate("NewPost")
}
AddBtn(icon = R.drawable.rider_pro_location_create, text = "Location Create")
}
}
@Composable
fun AddBtn(@DrawableRes icon: Int, text: String,onClick: (() -> Unit)? = {}){
Row (modifier = Modifier
.fillMaxWidth().padding(24.dp).clickable {
onClick?.invoke()
},
verticalAlignment = Alignment.CenterVertically){
Image(
modifier = Modifier.size(40.dp),
painter = painterResource(id = icon), contentDescription = null)
Text(modifier = Modifier.padding(start = 24.dp),text = text, color = Color.White,fontSize = 22.sp, style = TextStyle(fontWeight = FontWeight.Bold))
}
}

View File

@@ -0,0 +1,386 @@
package com.aiosman.riderpro.ui.index.tabs.moment
import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
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.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R
import com.aiosman.riderpro.model.MomentItem
import com.aiosman.riderpro.ui.comment.CommentModalContent
import com.aiosman.riderpro.ui.composables.AnimatedCounter
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon
import com.google.accompanist.systemuicontroller.rememberSystemUiController
private val DATA = (0..60).toList().mapIndexed { idx, _ ->
MomentItem(
id = idx,
avatar = R.drawable.default_avatar,
nickname = "Onyama Limba",
location = "Japan",
time = "2023.02.02 11:23",
followStatus = false,
momentTextContent = "By strongarming Ducati into giving him the factory seat.Marquez effectively …",
momentPicture = R.drawable.default_moment_img,
likeCount = 21,
commentCount = 43,
shareCount = 33,
favoriteCount = 211
)
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun MomentsList() {
val model = MomentViewModel
val moments = model.momentsFlow.collectAsLazyPagingItems()
LazyColumn {
items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentItem = momentItem)
}
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun MomentCard(
momentItem: MomentItem,
) {
val navController = LocalNavController.current
Column(
modifier = Modifier.fillMaxWidth()
) {
MomentTopRowGroup(momentItem = momentItem)
Column(
modifier = Modifier
.fillMaxWidth()
.clickable {
navController.navigate("Post/${momentItem.id}")
}
) {
MomentContentGroup(momentItem = momentItem)
}
val momentOperateBtnBoxModifier = Modifier
.fillMaxHeight()
.weight(1f)
ModificationListHeader()
MomentBottomOperateRowGroup(momentOperateBtnBoxModifier, momentItem = momentItem)
}
}
@Composable
fun ModificationListHeader() {
val navController = LocalNavController.current
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFF8F8F8))
.padding(4.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
navController.navigate("ModificationList")
}
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.background(Color(0xFFEB4869))
.padding(8.dp)
) {
Icon(
Icons.Filled.Build,
contentDescription = "Modification Icon",
tint = Color.White, // Assuming the icon should be white
modifier = Modifier.size(12.dp)
)
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Modification List",
color = Color(0xFF333333),
fontSize = 14.sp,
textAlign = TextAlign.Left
)
}
}
}
}
@Composable
fun MomentName(name: String) {
Text(
modifier = Modifier,
textAlign = TextAlign.Start,
text = name,
color = Color(0f, 0f, 0f),
fontSize = 16.sp, style = TextStyle(fontWeight = FontWeight.Bold)
)
}
@Composable
fun MomentFollowBtn() {
Box(
modifier = Modifier
.size(width = 53.dp, height = 18.dp)
.padding(start = 8.dp),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier
.fillMaxSize(),
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = ""
)
Text(
text = "Follow",
color = Color.White,
fontSize = 12.sp
)
}
}
@Composable
fun MomentPostLocation(location: String) {
Text(
text = location,
color = Color(0f, 0f, 0f, 0.6f),
fontSize = 12.sp
)
}
@Composable
fun MomentPostTime(time: String) {
Text(
modifier = Modifier.padding(start = 8.dp),
text = time, color = Color(0f, 0f, 0f, 0.6f),
fontSize = 12.sp
)
}
@Composable
fun MomentTopRowGroup(momentItem: MomentItem) {
Row(
modifier = Modifier
.height(40.dp)
.padding(top = 0.dp, bottom = 0.dp, start = 24.dp, end = 24.dp)
) {
Image(
painter = painterResource(id = momentItem.avatar),
contentDescription = ""
)
Column(
modifier = Modifier
.defaultMinSize()
.padding(start = 12.dp, end = 12.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(22.dp),
verticalAlignment = Alignment.CenterVertically
) {
MomentName(momentItem.nickname)
MomentFollowBtn()
}
Row(
modifier = Modifier
.fillMaxWidth()
.height(21.dp),
verticalAlignment = Alignment.CenterVertically
) {
MomentPostLocation(momentItem.location)
MomentPostTime(momentItem.time)
}
}
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun MomentContentGroup(
momentItem: MomentItem,
) {
val sharedTransitionScope = LocalSharedTransitionScope.current
val animatedContentScope = LocalAnimatedContentScope.current
with(sharedTransitionScope) {
Text(
text = momentItem.momentTextContent,
modifier = Modifier
.sharedElement(
sharedTransitionScope.rememberSharedContentState(key = "text-${momentItem.id}"),
animatedVisibilityScope = animatedContentScope
)
.fillMaxWidth()
.padding(top = 22.dp, bottom = 16.dp, start = 24.dp, end = 24.dp),
fontSize = 16.sp
)
Image(
modifier = Modifier
.sharedElement(
sharedTransitionScope.rememberSharedContentState(key = "image-${momentItem.id}"),
animatedVisibilityScope = animatedContentScope
)
.fillMaxWidth(),
painter = painterResource(id = momentItem.momentPicture),
contentDescription = ""
)
}
}
@Composable
fun MomentOperateBtn(@DrawableRes icon: Int, count: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = Modifier
.size(width = 24.dp, height = 24.dp),
painter = painterResource(id = icon),
contentDescription = ""
)
Text(
text = count,
modifier = Modifier.padding(start = 7.dp),
fontSize = 12.sp,
)
}
}
@Composable
fun MomentOperateBtn(count: String, content: @Composable () -> Unit) {
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically
) {
content()
AnimatedCounter(
count = count.toInt(),
fontSize = 14,
modifier = Modifier.padding(start = 7.dp)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MomentBottomOperateRowGroup(modifier: Modifier, momentItem: MomentItem) {
var systemUiController = rememberSystemUiController()
var showCommentModal by remember { mutableStateOf(false) }
if (showCommentModal) {
ModalBottomSheet(
onDismissRequest = { showCommentModal = false },
containerColor = Color.White,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
) {
systemUiController.setNavigationBarColor(Color(0xfff7f7f7))
CommentModalContent() {
systemUiController.setNavigationBarColor(Color.Black)
}
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
MomentOperateBtn(count = momentItem.likeCount.toString()) {
AnimatedLikeIcon(modifier = Modifier.size(24.dp)) {
MomentViewModel.updateById(
momentItem.id,
momentItem.copy(likeCount = momentItem.likeCount + 1)
)
}
}
}
Box(
modifier = modifier.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
showCommentModal = true
},
contentAlignment = Alignment.Center
) {
MomentOperateBtn(icon = R.drawable.rider_pro_moment_comment, count = "43")
}
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
MomentOperateBtn(icon = R.drawable.rider_pro_share, count = "33")
}
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
MomentOperateBtn(icon = R.drawable.rider_pro_favoriate, count = "211")
}
}
}
@Composable
fun MomentListLoading() {
CircularProgressIndicator(
modifier =
Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally),
color = Color.Red
)
}

View File

@@ -0,0 +1,59 @@
package com.aiosman.riderpro.ui.index.tabs.moment
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.moment.MomentPagingSource
import com.aiosman.riderpro.data.moment.MomentRemoteDataSource
import com.aiosman.riderpro.data.moment.TestMomentServiceImpl
import com.aiosman.riderpro.model.MomentItem
import kotlinx.coroutines.flow.Flow
private val DATA = (0..60).toList().mapIndexed { idx, _ ->
MomentItem(
id = idx,
avatar = R.drawable.default_avatar,
nickname = "Onyama Limba",
location = "Japan",
time = "2023.02.02 11:23",
followStatus = false,
momentTextContent = "By strongarming Ducati into giving him the factory seat.Marquez effectively …",
momentPicture = R.drawable.default_moment_img,
likeCount = 21,
commentCount = 43,
shareCount = 33,
favoriteCount = 211
)
}
object MomentViewModel : ViewModel() {
var momentList by mutableStateOf(DATA)
fun updateById(id: Int, momentItem: MomentItem) {
momentList = momentList.map {
if (it.id == id) {
momentItem
} else {
it
}
}.toMutableStateList()
}
var momentListPagingSource = MomentPagingSource(
MomentRemoteDataSource(TestMomentServiceImpl())
)
val momentsFlow: Flow<PagingData<MomentItem>> = Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { momentListPagingSource }
).flow
}

View File

@@ -0,0 +1,318 @@
package com.aiosman.riderpro.ui.index.tabs.profile
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.model.MomentItem
import com.aiosman.riderpro.model.profileMomentItems
@Composable
fun ProfilePage(){
Column (
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
){
CarGroup()
UserInformation()
RidingStyle()
UserMoment()
}
}
@Composable
fun CarGroup(){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 54.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CarTopInformation()
CarTopPicture()
}
}
@Composable
fun CarTopInformation(){
Row{
Text(text = "BMW", color = Color.Black, fontSize = 12.sp, style = TextStyle(fontWeight = FontWeight.Bold))
Text(modifier = Modifier.padding(start = 4.dp), text = "/", color = Color.Gray, fontSize = 12.sp)
Text(modifier = Modifier.padding(start = 4.dp), text = "M1000RR", color = Color.Gray, fontSize = 12.sp)
}
}
@Composable
fun CarTopPicture(){
Image(
modifier = Modifier
.size(width = 336.dp, height = 224.dp)
.padding(top = 42.dp),
painter = painterResource(id = R.drawable.default_profile_moto), contentDescription = "")
}
@Composable
fun UserInformation(){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 33.dp, end = 33.dp),
horizontalAlignment = Alignment.CenterHorizontally){
Row (modifier = Modifier.fillMaxWidth()){
val userInfoModifier = Modifier.weight(1f)
UserInformationFollowers(userInfoModifier)
UserInformationBasic(userInfoModifier)
UserInformationFollowing(userInfoModifier)
}
UserInformationSlogan()
CommunicationOperatorGroup()
}
}
@Composable
fun UserInformationFollowers(modifier: Modifier){
Column(modifier = modifier.padding(top = 31.dp)) {
Text(modifier = Modifier.padding(bottom = 5.dp),text = "183", fontSize = 24.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
Spacer(
modifier = Modifier
.size(width = 88.83.dp, height = 1.dp)
.border(width = 1.dp, color = Color.Gray)
.padding(top = 5.dp, bottom = 5.dp)
)
Text(modifier = Modifier.padding(top = 5.dp),text = "FOLLOWERS", fontSize = 12.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
}
}
@Composable
fun UserInformationBasic(modifier: Modifier){
Column(
horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.size(width = 112.dp, height = 112.dp),
contentAlignment = Alignment.Center
){
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.avatar_bold), contentDescription = "")
Image(
modifier = Modifier
.size(width = 88.dp, height = 88.dp)
.clip(
RoundedCornerShape(88.dp)
),
painter = painterResource(id = R.drawable.default_avatar), contentDescription = "")
}
Text(modifier = Modifier.padding(top = 8.dp), text = "Atom", fontSize = 32.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
Text(modifier = Modifier.padding(top = 4.dp), text = "America", fontSize = 12.sp, color = Color.Gray)
Text(modifier = Modifier.padding(top = 4.dp), text = "Member since Jun 4.2019", fontSize = 12.sp, color = Color.Gray)
}
}
@Composable
fun UserInformationFollowing(modifier: Modifier){
Column(modifier = modifier.padding(top = 6.dp),
horizontalAlignment = Alignment.End) {
Text(modifier = Modifier.padding(bottom = 5.dp), text = "306", fontSize = 24.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
Box(
modifier = Modifier
.size(width = 88.83.dp, height = 1.dp)
.border(width = 1.dp, color = Color.Gray)
)
Text(modifier = Modifier.padding(top = 5.dp),text = "FOLLOWING", fontSize = 12.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
}
}
@Composable
fun UserInformationSlogan(){
Text(modifier = Modifier.padding(top = 23.dp),text = "Ridering shooting fishing not much more", fontSize = 13.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
}
@Composable
fun CommunicationOperatorGroup(){
val navController = LocalNavController.current
Row (modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp), horizontalArrangement = Arrangement.Center){
Box(modifier = Modifier.size(width = 142.dp, height = 40.dp),
contentAlignment = Alignment.Center){
Image(modifier = Modifier.fillMaxSize(), painter = painterResource(id = R.drawable.rider_pro_profile_follow), contentDescription = "")
Text(text = "FOLLOW", fontSize = 16.sp, color = Color.White, style = TextStyle(fontWeight = FontWeight.Bold))
}
Box(modifier = Modifier
.size(width = 142.dp, height = 40.dp)
.padding(start = 6.dp).clickable {
navController.navigate("MyMessage")
},
contentAlignment = Alignment.Center){
Image(modifier = Modifier.fillMaxSize(),painter = painterResource(id = R.drawable.rider_pro_profile_message), contentDescription = "")
Text(text = "MESSAGE", fontSize = 16.sp, color = Color.White, style = TextStyle(fontWeight = FontWeight.Bold))
}
Box(modifier = Modifier.size(width = 142.dp, height = 40.dp).clickable {
navController.navigate("ProfileTimeline")
},
contentAlignment = Alignment.Center){
Image(modifier = Modifier.fillMaxSize(), painter = painterResource(id = R.drawable.rider_pro_profile_follow), contentDescription = "")
Text(text = "GALLERY", fontSize = 16.sp, color = Color.White, style = TextStyle(fontWeight = FontWeight.Bold))
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RidingStyle(){
Column(modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 40.dp, end = 24.dp),
horizontalAlignment = Alignment.Start) {
Text(text = "RIDING STYLES", fontSize = 18.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
Image(modifier = Modifier
.padding(top = 4.dp)
.height(8.dp),painter = painterResource(id = R.drawable.rider_pro_profile_line), contentDescription = "")
FlowRow (modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)){
RidingStyleItem(styleContent = "Cruiser")
RidingStyleItem(styleContent = "Bobber")
RidingStyleItem(styleContent = "Cafe")
RidingStyleItem(styleContent = "Chopper")
RidingStyleItem(styleContent = "Sport")
RidingStyleItem(styleContent = "Vintage")
RidingStyleItem(styleContent = "Trike")
RidingStyleItem(styleContent = "Touring")
}
}
}
@Composable
fun RidingStyleItem(styleContent: String){
Box(modifier = Modifier.padding(bottom = 8.dp, end = 8.dp),
contentAlignment = Alignment.Center) {
Image(modifier = Modifier.shadow(
ambientColor = Color.Gray,
spotColor = Color(0f,0f,0f,0.2f),
elevation = 20.dp,
), painter = painterResource(id = R.drawable.rider_pro_style_wrapper), contentDescription = "")
Text(modifier = Modifier.padding(start = 5.dp, end = 5.dp), text = styleContent, fontSize = 12.sp, color = Color.Gray, style = TextStyle(fontWeight = FontWeight.Bold))
}
}
@Composable
fun UserMoment(){
profileMomentItems.forEach(
action = { MomentPostUnit(it) }
)
}
@Composable
fun MomentPostUnit(momentItem: MomentItem){
TimeGroup(momentItem.time)
MomentCard(momentItem.momentTextContent,
momentItem.momentPicture,
momentItem.likeCount.toString(),
momentItem.commentCount.toString())
}
@Composable
fun TimeGroup(time: String = "2024.06.08 12:23"){
Row(modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 40.dp, end = 24.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically){
Image(
modifier = Modifier.padding(end = 12.dp),
painter = painterResource(id = R.drawable.rider_pro_moment_time_flag), contentDescription = "")
Text(text = time,fontSize = 22.sp, color = Color.Black, style = TextStyle(fontWeight = FontWeight.Bold))
}
}
@Composable
fun MomentCard(content: String,@DrawableRes picture: Int, like: String, comment: String){
Column(modifier = Modifier
.fillMaxWidth()
.padding(start = 48.dp, top = 18.dp, end = 24.dp)
.border(width = 1.dp, color = Color(0f,0f,0f,0.1f), shape = RoundedCornerShape(6.dp))
){
MomentCardTopContent(content)
MomentCardPicture(picture)
MomentCardOperation(like,comment)
}
}
@Composable
fun MomentCardTopContent(content: String){
Row(modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically){
Text(
modifier = Modifier.padding(16.dp),
text = content,fontSize = 16.sp, color = Color.Black)
}
}
@Composable
fun MomentCardPicture(@DrawableRes drawable: Int){
Image(modifier = Modifier
.fillMaxSize()
.padding(16.dp), painter = painterResource(id = drawable), contentDescription = "")
}
@Composable
fun MomentCardOperation(like: String, comment: String){
Row(modifier = Modifier
.fillMaxWidth()
.height(56.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically){
Spacer(modifier = Modifier.weight(1f))
MomentCardOperationItem(drawable = R.drawable.rider_pro_like, number = like, modifier = Modifier.weight(1f))
MomentCardOperationItem(drawable = R.drawable.rider_pro_moment_comment, number = comment, modifier = Modifier.weight(1f))
}
}
@Composable
fun MomentCardOperationItem(@DrawableRes drawable: Int, number: String, modifier: Modifier){
Row(modifier = modifier,
verticalAlignment = Alignment.CenterVertically){
Image(modifier = Modifier.padding(start = 16.dp, end = 8.dp),
painter = painterResource(id = drawable), contentDescription = "")
Text(text = number)
}
}

View File

@@ -0,0 +1,231 @@
package com.aiosman.riderpro.ui.index.tabs.shorts
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.roundToInt
class PagerState(
currentPage: Int = 0,
minPage: Int = 0,
maxPage: Int = 0
) {
private var _minPage by mutableStateOf(minPage)
var minPage: Int
get() = _minPage
set(value) {
_minPage = value.coerceAtMost(_maxPage)
_currentPage = _currentPage.coerceIn(_minPage, _maxPage)
}
private var _maxPage by mutableStateOf(maxPage, structuralEqualityPolicy())
var maxPage: Int
get() = _maxPage
set(value) {
_maxPage = value.coerceAtLeast(_minPage)
_currentPage = _currentPage.coerceIn(_minPage, maxPage)
}
private var _currentPage by mutableStateOf(currentPage.coerceIn(minPage, maxPage))
var currentPage: Int
get() = _currentPage
set(value) {
_currentPage = value.coerceIn(minPage, maxPage)
}
enum class SelectionState { Selected, Undecided }
var selectionState by mutableStateOf(SelectionState.Selected)
suspend inline fun <R> selectPage(block: PagerState.() -> R): R = try {
selectionState = SelectionState.Undecided
block()
} finally {
selectPage()
}
suspend fun selectPage() {
currentPage -= currentPageOffset.roundToInt()
snapToOffset(0f)
selectionState = SelectionState.Selected
}
private var _currentPageOffset = Animatable(0f).apply {
updateBounds(-1f, 1f)
}
val currentPageOffset: Float
get() = _currentPageOffset.value
suspend fun snapToOffset(offset: Float) {
val max = if (currentPage == minPage) 0f else 1f
val min = if (currentPage == maxPage) 0f else -1f
_currentPageOffset.snapTo(offset.coerceIn(min, max))
}
suspend fun fling(velocity: Float) {
if (velocity < 0 && currentPage == maxPage) return
if (velocity > 0 && currentPage == minPage) return
// 根据 fling 的方向滑动到下一页或上一页
_currentPageOffset.animateTo(velocity)
selectPage()
}
override fun toString(): String = "PagerState{minPage=$minPage, maxPage=$maxPage, " +
"currentPage=$currentPage, currentPageOffset=$currentPageOffset}"
}
@Immutable
private data class PageData(val page: Int) : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? = this@PageData
}
private val Measurable.page: Int
get() = (parentData as? PageData)?.page ?: error("no PageData for measurable $this")
@Composable
fun Pager(
modifier: Modifier = Modifier,
state: PagerState,
orientation: Orientation = Orientation.Horizontal,
offscreenLimit: Int = 2,
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, // 新增水平对齐参数
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, // 新增垂直对齐参数
content: @Composable PagerScope.() -> Unit
) {
var pageSize by remember { mutableStateOf(0) }
val coroutineScope = rememberCoroutineScope()
Layout(
content = {
// 根据 offscreenLimit 计算页面范围
val minPage = maxOf(state.currentPage - offscreenLimit, state.minPage)
val maxPage = minOf(state.currentPage + offscreenLimit, state.maxPage)
for (page in minPage..maxPage) {
val pageData = PageData(page)
val scope = PagerScope(state, page)
key(pageData) {
Column(modifier = pageData) {
scope.content()
}
}
}
},
modifier = modifier.draggable(
orientation = orientation,
onDragStarted = {
state.selectionState = PagerState.SelectionState.Undecided
},
onDragStopped = { velocity ->
coroutineScope.launch {
// 根据速度判断是否滑动到下一页
val threshold = 1000f // 速度阈值,可调整
if (velocity > threshold) {
state.fling(1f) // 向右滑动
} else if (velocity < -threshold) {
state.fling(-1f) // 向左滑动
} else {
state.fling(0f) // 保持当前页
}
}
},
state = rememberDraggableState { dy ->
coroutineScope.launch {
with(state) {
val pos = pageSize * currentPageOffset
val max = if (currentPage == minPage) 0 else pageSize
val min = if (currentPage == maxPage) 0 else -pageSize
// 直接将手指的位移应用到 currentPageOffset
val newPos = (pos + dy).coerceIn(min.toFloat(), max.toFloat())
snapToOffset(newPos / pageSize)
}
}
},
)
) { measurables, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {
val currentPage = state.currentPage
val offset = state.currentPageOffset
val childConstraints = constraints.copy(minWidth = 0, minHeight = 0)
measurables.forEach { measurable ->
val placeable = measurable.measure(childConstraints)
val page = measurable.page
// 根据对齐参数计算 x 和 y 位置
val xPosition = when (horizontalAlignment) {
Alignment.Start -> 0
Alignment.CenterHorizontally -> (constraints.maxWidth - placeable.width) / 2
Alignment.End -> constraints.maxWidth - placeable.width
else -> 0
}
val yPosition = when (verticalAlignment) {
Alignment.Top -> 0
Alignment.CenterVertically -> (constraints.maxHeight - placeable.height) / 2
Alignment.Bottom -> constraints.maxHeight - placeable.height
else -> 0
}
if (currentPage == page) { // 只在当前页面设置 pageSize避免不必要的设置
pageSize = if (orientation == Orientation.Horizontal) {
placeable.width
} else {
placeable.height
}
}
val isVisible = abs(page - (currentPage - offset)) <= 1
if (isVisible) {
// 修正 x 的计算
val xOffset = if (orientation == Orientation.Horizontal) {
((page - currentPage) * pageSize + offset * pageSize).roundToInt()
} else {
0
}
// 使用 placeRelative 进行放置
placeable.placeRelative(
x = xPosition + xOffset,
y = yPosition + if (orientation == Orientation.Vertical) ((page - (currentPage - offset)) * placeable.height).roundToInt() else 0
)
}
}
}
}
}
class PagerScope(
private val state: PagerState,
val page: Int
) {
val currentPage: Int
get() = state.currentPage
val currentPageOffset: Float
get() = state.currentPageOffset
val selectionState: PagerState.SelectionState
get() = state.selectionState
}

View File

@@ -0,0 +1,69 @@
package com.aiosman.riderpro.ui.index.tabs.shorts
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import com.aiosman.riderpro.ui.theme.RiderProTheme
val videoUrls = listOf(
"https://api.aiosman.com/video/1.mp4",
"https://api.aiosman.com/video/2.mp4",
"https://api.aiosman.com/video/3.mp4",
"https://api.aiosman.com/video/4.mp4",
"https://api.aiosman.com/video/5.webm",
"https://api.aiosman.com/video/6.webm",
"https://api.aiosman.com/video/7.webm",
"https://api.aiosman.com/video/8.webm",
"https://api.aiosman.com/video/9.webm",
"https://api.aiosman.com/video/10.webm",
"https://api.aiosman.com/video/11.webm",
"https://api.aiosman.com/video/12.webm",
"https://api.aiosman.com/video/13.webm",
"https://api.aiosman.com/video/14.webm",
"https://api.aiosman.com/video/15.webm",
"https://api.aiosman.com/video/16.webm",
"https://api.aiosman.com/video/17.webm",
"https://api.aiosman.com/video/18.webm",
"https://api.aiosman.com/video/19.webm",
"https://api.aiosman.com/video/20.webm",
"https://api.aiosman.com/video/21.webm",
"https://api.aiosman.com/video/22.webm",
"https://api.aiosman.com/video/23.webm",
"https://api.aiosman.com/video/24.webm",
"https://api.aiosman.com/video/25.webm",
"https://api.aiosman.com/video/26.webm",
"https://api.aiosman.com/video/27.webm",
"https://api.aiosman.com/video/28.webm",
"https://api.aiosman.com/video/29.webm",
"https://api.aiosman.com/video/30.webm",
"https://api.aiosman.com/video/31.webm",
"https://api.aiosman.com/video/32.webm",
"https://api.aiosman.com/video/33.webm",
"https://api.aiosman.com/video/34.webm",
"https://api.aiosman.com/video/35.webm",
"https://api.aiosman.com/video/36.webm",
"https://api.aiosman.com/video/37.webm",
"https://api.aiosman.com/video/38.webm",
"https://api.aiosman.com/video/39.webm",
"https://api.aiosman.com/video/40.webm",
"https://api.aiosman.com/video/41.webm",
)
@Composable
fun ShortVideo() {
RiderProTheme {
Surface(color = MaterialTheme.colorScheme.background) {
ShortViewCompose(
videoItemsUrl = videoUrls,
clickItemPosition = 0
)
}
}
}

View File

@@ -0,0 +1,392 @@
@file:kotlin.OptIn(ExperimentalMaterial3Api::class)
package com.aiosman.riderpro.ui.index.tabs.shorts
import android.net.Uri
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.DrawableRes
import androidx.annotation.OptIn
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
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.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSourceFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.comment.CommentModalContent
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun ShortViewCompose(
videoItemsUrl: List<String>,
clickItemPosition: Int = 0,
videoHeader: @Composable () -> Unit = {},
videoBottom: @Composable () -> Unit = {}
) {
val pagerState: PagerState = run {
remember {
PagerState(clickItemPosition, 0, videoItemsUrl.size - 1)
}
}
val initialLayout = remember {
mutableStateOf(true)
}
val pauseIconVisibleState = remember {
mutableStateOf(false)
}
Pager(
state = pagerState,
orientation = Orientation.Vertical,
offscreenLimit = 1
) {
pauseIconVisibleState.value = false
SingleVideoItemContent(
videoItemsUrl[page],
pagerState,
page,
initialLayout,
pauseIconVisibleState,
videoHeader,
videoBottom
)
}
LaunchedEffect(clickItemPosition) {
delay(300)
initialLayout.value = false
}
}
@Composable
private fun SingleVideoItemContent(
videoUrl: String,
pagerState: PagerState,
pager: Int,
initialLayout: MutableState<Boolean>,
pauseIconVisibleState: MutableState<Boolean>,
VideoHeader: @Composable() () -> Unit,
VideoBottom: @Composable() () -> Unit,
) {
Box(modifier = Modifier.fillMaxSize()) {
VideoPlayer(videoUrl, pagerState, pager, pauseIconVisibleState)
VideoHeader.invoke()
Box(modifier = Modifier.align(Alignment.BottomStart)) {
VideoBottom.invoke()
}
if (initialLayout.value) {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = Color.Black)
)
}
}
}
@OptIn(UnstableApi::class)
@Composable
fun VideoPlayer(
videoUrl: String,
pagerState: PagerState,
pager: Int,
pauseIconVisibleState: MutableState<Boolean>,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val lifecycleOwner = LocalLifecycleOwner.current
var showCommentModal by remember { mutableStateOf(false) }
var sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(
context,
Util.getUserAgent(context, context.packageName)
)
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)))
this.prepare(source)
}
}
if (pager == pagerState.currentPage) {
exoPlayer.playWhenReady = true
exoPlayer.play()
} else {
exoPlayer.pause()
}
exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
exoPlayer.repeatMode = Player.REPEAT_MODE_ONE
// player box
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.TopCenter
) {
var playerView by remember { mutableStateOf<PlayerView?>(null) } // Store reference to PlayerView
AndroidView(
factory = { context ->
// 创建一个 FrameLayout 作为容器
FrameLayout(context).apply {
// 设置背景颜色为黑色,用于显示黑边
setBackgroundColor(Color.Black.toArgb())
// 创建 PlayerView 并添加到 FrameLayout 中
val view = PlayerView(context).apply {
hideController()
useController = false
player = exoPlayer
resizeMode =
AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT // 或 RESIZE_MODE_ZOOM
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER
)
}
addView(view)
}
},
modifier = Modifier.noRippleClickable {
pauseIconVisibleState.value = true
exoPlayer.pause()
scope.launch {
delay(100)
if (exoPlayer.isPlaying) {
exoPlayer.pause()
} else {
pauseIconVisibleState.value = false
exoPlayer.play()
}
}
}
)
if (pauseIconVisibleState.value) {
Icon(
imageVector = Icons.Default.PlayArrow,
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.size(80.dp)
)
}
// Release ExoPlayer when the videoUrl changes or the composable leaves composition
DisposableEffect(videoUrl) {
onDispose {
exoPlayer.release()
playerView?.player = null
playerView = null // Release the reference to the PlayerView
}
}
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_PAUSE -> {
exoPlayer.pause() // 应用进入后台时暂停
}
Lifecycle.Event.ON_RESUME -> {
if (pager == pagerState.currentPage) {
exoPlayer.play() // 返回前台且为当前页面时恢复播放
}
}
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
// action buttons
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomEnd
) {
Column(
modifier = Modifier.padding(bottom = 72.dp, end = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
UserAvatar()
VideoBtn(icon = R.drawable.rider_pro_video_like, text = "975.9k")
VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "1896") {
showCommentModal = true
}
VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "234")
VideoBtn(icon = R.drawable.rider_pro_video_share, text = "677k")
}
}
// info
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomStart
) {
Column(modifier = Modifier.padding(start = 16.dp, bottom = 16.dp)) {
Row(
modifier = Modifier
.padding(bottom = 8.dp)
.background(color = Color.Gray),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start,
) {
Image(
modifier = Modifier
.size(20.dp)
.padding(start = 4.dp, end = 6.dp),
painter = painterResource(id = R.drawable.rider_pro_video_location),
contentDescription = ""
)
Text(
modifier = Modifier.padding(end = 4.dp),
text = "USA",
fontSize = 12.sp,
color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
Text(
text = "@Kevinlinpr",
fontSize = 16.sp,
color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold)
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp), // 确保Text占用可用宽度
text = "Pedro Acosta to join KTM in 2025 on a multi-year deal! \uD83D\uDFE0",
fontSize = 16.sp,
color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold),
overflow = TextOverflow.Ellipsis, // 超出范围时显示省略号
maxLines = 2 // 最多显示两行
)
}
}
if (showCommentModal) {
ModalBottomSheet(
onDismissRequest = { showCommentModal = false },
containerColor = Color.White,
sheetState = sheetState
) {
CommentModalContent() {
}
}
}
}
@Composable
fun UserAvatar() {
Image(
modifier = Modifier
.padding(bottom = 16.dp)
.size(40.dp)
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
.clip(
RoundedCornerShape(40.dp)
), painter = painterResource(id = R.drawable.default_avatar), contentDescription = ""
)
}
@Composable
fun VideoBtn(@DrawableRes icon: Int, text: String, onClick: (() -> Unit)? = null) {
Column(
modifier = Modifier
.padding(bottom = 16.dp)
.clickable {
onClick?.invoke()
},
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
modifier = Modifier.size(36.dp),
painter = painterResource(id = icon),
contentDescription = ""
)
Text(
text = text,
fontSize = 11.sp,
color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}

View File

@@ -0,0 +1,268 @@
package com.aiosman.riderpro.ui.index.tabs.street
import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import androidx.navigation.NavOptions
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.test.countries
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapProperties
import com.google.maps.android.compose.MapUiSettings
import com.google.maps.android.compose.MarkerComposable
import com.google.maps.android.compose.MarkerState
import com.google.maps.android.compose.rememberCameraPositionState
@Composable
fun StreetPage() {
val navController = LocalNavController.current
val context = LocalContext.current
val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
var currentLocation by remember { mutableStateOf<LatLng?>(null) }
val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp()
}
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(currentLocation ?: LatLng(0.0, 0.0), 10f)
}
var hasLocationPermission by remember { mutableStateOf(false) }
var searchText by remember { mutableStateOf("") }
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted ->
hasLocationPermission = isGranted
if (isGranted) {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
currentLocation = LatLng(location.latitude, location.longitude)
}
}
}
}
)
LaunchedEffect(Unit) {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_FINE_LOCATION
) -> {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
// currentLocation = LatLng(location.latitude, location.longitude)
}
}
hasLocationPermission = true
}
else -> {
permissionLauncher.launch(android.Manifest.permission.ACCESS_FINE_LOCATION)
}
}
}
LaunchedEffect(currentLocation) {
cameraPositionState.position =
CameraPosition.fromLatLngZoom(
currentLocation ?: LatLng(
countries[0].lat,
countries[0].lng
), 5f
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 56.dp + navigationBarHeight)
) {
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState,
properties = MapProperties(
isMyLocationEnabled = hasLocationPermission,
),
uiSettings = MapUiSettings(
compassEnabled = true,
myLocationButtonEnabled = false,
zoomControlsEnabled = false
)
) {
// pins
countries.forEach { position ->
MarkerComposable(
state = MarkerState(position = LatLng(position.lat, position.lng)),
onClick = {
val screenLocation =
cameraPositionState.projection?.toScreenLocation(it.position)
val x = screenLocation?.x ?: 0
val y = screenLocation?.y ?: 0
navController.navigate("LocationDetail/${x}/${y}",NavOptions.Builder()
.setEnterAnim(0)
.setExitAnim(0)
.setPopEnterAnim(0)
.setPopExitAnim(0)
.build())
true
},
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_map_mark),
contentDescription = "",
)
}
}
}
Image(
painter = painterResource(id = R.drawable.rider_pro_my_location),
contentDescription = "",
modifier = Modifier
.align(Alignment.BottomStart)
.padding(start = 16.dp, bottom = 16.dp + navigationBarHeight)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
currentLocation?.let {
cameraPositionState.position =
CameraPosition.fromLatLngZoom(it, cameraPositionState.position.zoom)
}
}
)
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 16.dp, bottom = 16.dp + navigationBarHeight)
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(color = Color(0xffda3832))
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = "",
modifier = Modifier
.align(Alignment.Center)
.size(36.dp),
colorFilter = ColorFilter.tint(Color.White)
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.TopCenter)
.padding(top = 64.dp, start = 16.dp, end = 16.dp)
) {
Box(
modifier = Modifier
.background(Color.White)
.padding(16.dp),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(Color(0xfff7f7f7))
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_search_location),
contentDescription = "",
tint = Color(0xffc6c6c6),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.fillMaxWidth()
) {
if (searchText.isEmpty()) {
Text(
text = "Please enter a search location",
color = Color(0xffc6c6c6),
fontSize = 16.sp,
modifier = Modifier.padding(start = 8.dp)
)
}
BasicTextField(
value = searchText,
onValueChange = {
searchText = it
},
modifier = Modifier
.fillMaxWidth(),
textStyle = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Normal
)
)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,132 @@
package com.aiosman.riderpro.ui.like
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
@Preview
@Composable
fun LikeScreen() {
val model = LikePageViewModel
val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState()
// observe list scrolling
val reachedBottom: Boolean by remember {
derivedStateOf {
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 3
}
}
LaunchedEffect(Unit) {
LikePageViewModel.loader.loadData()
}
LaunchedEffect(reachedBottom) {
if (reachedBottom) LikePageViewModel.loader.loadMore()
}
StatusBarMaskLayout(
darkIcons = true,
maskBoxBackgroundColor = Color(0xFFFFFFFF)
) {
Column(
modifier = Modifier
.weight(1f)
.background(color = Color(0xFFFFFFFF))
.padding(horizontal = 16.dp)
) {
NoticeScreenHeader("LIKES")
Spacer(modifier = Modifier.height(28.dp))
LazyColumn(
modifier = Modifier.weight(1f),
state = listState,
) {
items(LikePageViewModel.loader.list, key = { it.id }) {
LikeItem(it)
}
item {
BottomNavigationPlaceholder()
}
}
}
}
}
@Composable
fun LikeItem(itemData: LikeItemData) {
Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Like",
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text(itemData.name, fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(5.dp))
Text("Username", fontSize = 12.sp, color = Color(0x99000000))
}
Box {
Image(
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = "Like",
modifier = Modifier
.width(79.dp)
.height(24.dp)
)
Text(
"FOLLOW",
fontSize = 14.sp,
color = Color(0xFFFFFFFF),
modifier = Modifier.align(
Alignment.Center
)
)
}
}
}
}

View File

@@ -0,0 +1,68 @@
package com.aiosman.riderpro.ui.like
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.aiosman.riderpro.test.MockDataContainer
import com.aiosman.riderpro.test.MockDataSource
import com.aiosman.riderpro.test.MockListContainer
class LikeDataSource : MockDataSource<LikeItemData>() {
init {
var newList = mutableListOf<LikeItemData>()
for (i in 0..999) {
newList.add(LikeItemData(i, "Like $i"))
}
list = newList
}
}
abstract class DataLoader<T> {
var list = mutableListOf<T>()
var page by mutableStateOf(1)
var pageSize by mutableStateOf(20)
var total by mutableStateOf(0)
var noMoreData by mutableStateOf(false)
suspend fun loadData() {
val resp = fetchData(page, pageSize)
if (resp.success) {
resp.data?.let {
total = it.total
list = it.list.toMutableList()
if (it.list.size < pageSize) {
noMoreData = true
}
}
}
}
suspend fun loadMore(){
if (list.size >= total) return
page++
val resp = fetchData(page, pageSize)
if (resp.success) {
resp.data?.let {
total = it.total
list.addAll(it.list)
if (it.list.size < pageSize) {
noMoreData = true
}
}
}
}
abstract suspend fun fetchData(page: Int, pageSize: Int): MockDataContainer<MockListContainer<T>>
}
class LikeListLoader : DataLoader<LikeItemData>() {
var mockDataSource = LikeDataSource()
override suspend fun fetchData(page: Int, pageSize: Int): MockDataContainer<MockListContainer<LikeItemData>> {
return mockDataSource.fetchData(page, pageSize)
}
}
object LikePageViewModel : ViewModel() {
val loader = LikeListLoader()
}
data class LikeItemData(
val id: Int,
val name: String,
)

View File

@@ -0,0 +1,456 @@
package com.aiosman.riderpro.ui.location
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
data class OfficialGalleryItem(
val id: Int,
val resId: Int,
)
fun getOfficialGalleryItems(): List<OfficialGalleryItem> {
return listOf(
OfficialGalleryItem(1, R.drawable.default_moment_img),
OfficialGalleryItem(2, R.drawable.default_moment_img),
OfficialGalleryItem(3, R.drawable.default_moment_img),
OfficialGalleryItem(4, R.drawable.default_moment_img),
OfficialGalleryItem(5, R.drawable.default_moment_img),
OfficialGalleryItem(6, R.drawable.default_moment_img),
OfficialGalleryItem(7, R.drawable.default_moment_img),
OfficialGalleryItem(8, R.drawable.default_moment_img),
OfficialGalleryItem(9, R.drawable.default_moment_img),
OfficialGalleryItem(10, R.drawable.default_moment_img),
OfficialGalleryItem(11, R.drawable.default_moment_img),
OfficialGalleryItem(12, R.drawable.default_moment_img),
OfficialGalleryItem(13, R.drawable.default_moment_img),
OfficialGalleryItem(14, R.drawable.default_moment_img),
OfficialGalleryItem(15, R.drawable.default_moment_img),
OfficialGalleryItem(16, R.drawable.default_moment_img),
OfficialGalleryItem(17, R.drawable.default_moment_img),
OfficialGalleryItem(18, R.drawable.default_moment_img),
OfficialGalleryItem(19, R.drawable.default_moment_img),
OfficialGalleryItem(20, R.drawable.default_moment_img),
)
}
data class FeedItem(
val id: Int,
val resId: Int,
)
fun getFeedItems(): List<FeedItem> {
val image_pickups = listOf(
R.drawable.default_moment_img,
R.drawable.default_avatar,
R.drawable.rider_pro_moment_demo_1,
R.drawable.rider_pro_moment_demo_2,
R.drawable.rider_pro_moment_demo_3,
)
return (1..100).map {
FeedItem(it, image_pickups.random())
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun LocationDetailScreen(x: Float, y: Float) {
val scope = rememberCoroutineScope()
val scaffoldState = rememberBottomSheetScaffoldState(
SheetState(
skipPartiallyExpanded = false,
density = LocalDensity.current, initialValue = SheetValue.PartiallyExpanded,
skipHiddenState = true
)
)
val configuration = LocalConfiguration.current
val officialGalleryItems = getOfficialGalleryItems()
val feedItems = getFeedItems()
val navController = LocalNavController.current
// 2/3 height of the screen
fun getPeekHeight(): Dp {
val screenHeight = configuration.screenHeightDp
val peekHeight = (screenHeight * 2 / 3).dp
return peekHeight
}
fun getNoPeekHeight(): Dp {
val screenHeight = configuration.screenHeightDp
val peekHeight = (screenHeight * 1 / 3).dp
return peekHeight
}
val view = LocalView.current
// LaunchedEffect(key1 = Unit) {
// val locationOnScreen = IntArray(2).apply {
// view.getLocationOnScreen(this)
// }
// val startX = x - locationOnScreen[0]
// val startY = y - locationOnScreen[1]
// val radius = hypot(view.width.toDouble(), view.height.toDouble()).toFloat()
//
// val anim = ViewAnimationUtils.createCircularReveal(view, startX.toInt(), startY.toInt(), 0f, radius).apply {
// duration = 600
// start()
// }
//
// }
val staggeredGridState = rememberLazyStaggeredGridState()
val coroutineScope = rememberCoroutineScope()
var showGalleryAndInfo by remember { mutableStateOf(true) }
LaunchedEffect(staggeredGridState) {
snapshotFlow { staggeredGridState.firstVisibleItemIndex }
.collect { index ->
// Assuming index 0 corresponds to the top of the feed
showGalleryAndInfo = index == 0
}
}
Box(
modifier = Modifier.fillMaxSize().background(Color.Transparent)
) {
Image(
painter = painterResource(id = R.drawable.default_moment_img),
contentDescription = "Location Image",
modifier = Modifier
.fillMaxWidth()
.height(getNoPeekHeight() + 100.dp),
contentScale = ContentScale.Crop
)
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState()
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetPeekHeight = getPeekHeight(),
sheetShadowElevation = 0.dp,
sheetContainerColor = Color.Transparent,
sheetShape = RoundedCornerShape(16.dp, 16.dp, 0.dp, 0.dp),
sheetDragHandle = null,
sheetContent = {
Column(
Modifier
.fillMaxWidth()
.background(color = Color.White)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(12.dp))
// 自定义短线
Box(
modifier = Modifier
.width(32.dp) // 修改宽度
.height(4.dp) // 修改高度
.background(
Color(0f, 0f, 0f, 0.4f),
RoundedCornerShape(3.dp)
) // 修改颜色和圆角
.padding(top = 12.dp) // 调整位置
.align(Alignment.CenterHorizontally)
)
}
Spacer(modifier = Modifier.height(16.dp))
GalleryAndInfo(showGalleryAndInfo)
// feed
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
.animateContentSize()
) {
AnimatedVisibility(visible = !showGalleryAndInfo) {
Row {
Icon(
Icons.Filled.LocationOn,
contentDescription = "Location",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text("在云龟山景区的", fontSize = 16.sp)
}
}
Text(
"车友动态",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
)
}
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2), // Set to 2 columns
modifier = Modifier.fillMaxSize(),
state = staggeredGridState,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
flingBehavior = ScrollableDefaults.flingBehavior(),
) {
items(feedItems) { item ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
Image(
painter = painterResource(id = item.resId),
contentDescription = "Feed",
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.medium)
.clickable {
navController.navigate("Post")
},
contentScale = ContentScale.FillWidth
)
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
) {
Box(
modifier = Modifier.fillMaxWidth()
) {
Text("Some text")
}
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Avatar",
modifier = Modifier
.size(18.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(8.dp))
Text(
"Username",
color = Color(0xFF666666),
fontSize = 14.sp
)
Spacer(modifier = Modifier.weight(1f))
Icon(
Icons.Filled.Favorite,
contentDescription = "Location"
)
Spacer(modifier = Modifier.width(4.dp))
Text("100K", fontSize = 14.sp)
}
}
}
}
}
}
},
) {
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun GalleryAndInfo(showGalleryAndInfo: Boolean) {
val navController = LocalNavController.current
Column(modifier = Modifier.animateContentSize()) {
AnimatedVisibility(visible = showGalleryAndInfo) {
Column {
// info panel
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
Text(
"Location Name",
modifier = Modifier.padding(top = 24.dp),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
repeat(10) {
Box(
modifier = Modifier
.background(Color(0xFFF5F5F5))
.padding(horizontal = 7.dp, vertical = 2.dp)
) {
Text("Tag $it", color = Color(0xFFb2b2b2), fontSize = 12.sp)
}
}
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp),
color = Color(0xFFF5F5F5)
)
Row(
modifier = Modifier.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
"Location name",
fontSize = 14.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text("距离46KM,骑行时间77分钟", fontSize = 12.sp)
}
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_location_map),
contentDescription = ""
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(8.dp)
.background(Color(0xFFF5F5F5))
)
// official gallery
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 18.dp)
) {
Row {
Text("官方摄影师作品", fontSize = 15.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_next),
contentDescription = "Next",
modifier = Modifier
.size(24.dp)
.clickable {
navController.navigate("OfficialPhoto")
}
)
}
Spacer(modifier = Modifier.height(17.dp))
Row(
modifier = Modifier.height(232.dp)
) {
Box(
modifier = Modifier.weight(1f)
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Avatar",
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(16.dp)),
contentScale = ContentScale.Crop,
)
}
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier.weight(1f)
) {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "Avatar",
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(16.dp)),
contentScale = ContentScale.Crop
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.height(8.dp)
.background(Color(0xFFF5F5F5))
)
}
}
}
}

View File

@@ -0,0 +1,228 @@
package com.aiosman.riderpro.ui.message
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.LoadState
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.ui.index.tabs.moment.MomentListLoading
import com.aiosman.riderpro.R
import com.aiosman.riderpro.model.ChatNotificationData
import com.aiosman.riderpro.model.TestChatBackend
val chatNotificationData = ChatNotificationData(
R.drawable.default_avatar,
"Tokunaga Yae",
"Memphis",
"3 minutes ago",
6
)
private val ChatData = (0..10).toList().map { chatNotificationData }
@Composable
fun MessagePage(){
val myBackend = remember { TestChatBackend(ChatData) }
val pager = remember {
Pager(
PagingConfig(
pageSize = myBackend.DataBatchSize,
enablePlaceholders = true,
maxSize = 200
)
) {
myBackend.getAllData()
}
}
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
MessageTopSwitchBtnGroup()
MessageBarrierLine()
MessageNotification()
LazyColumn (
modifier = Modifier.padding(bottom = 20.dp)
){
if (lazyPagingItems.loadState.refresh == LoadState.Loading) {
item {
MomentListLoading()
}
}
items(count = lazyPagingItems.itemCount) { index ->
val item = lazyPagingItems[index]
if (item != null) {
ChatItem(item)
}
}
if (lazyPagingItems.loadState.append == LoadState.Loading) {
item {
MomentListLoading()
}
}
}
}
@Composable
fun MessageTopSwitchBtnGroup(){
Column (
modifier = Modifier
.fillMaxWidth()
.height(113.dp)
) {
Row(modifier = Modifier.fillMaxSize()){
val notificationBtnModifier = Modifier
.fillMaxHeight()
.weight(1f)
NotificationBtn(notificationBtnModifier,drawableId = R.drawable.rider_pro_like,
displayText = "LIKE")
NotificationBtn(notificationBtnModifier,drawableId = R.drawable.rider_pro_followers,
displayText = "FOLLOWERS")
NotificationBtn(notificationBtnModifier,drawableId = R.drawable.rider_pro_comments,
displayText = "COMMENTS")
}
}
}
@Composable
fun MessageBarrierLine(){
Box(modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.padding(start = 24.dp, end = 24.dp)
.border(width = 1.dp, Color(0f, 0f, 0f, 0.1f))
)
}
@Composable
fun NotificationBtn(modifier: Modifier, drawableId: Int, displayText: String){
Box(modifier = modifier,
contentAlignment = Alignment.Center){
Column(modifier = Modifier
.size(width = 79.dp, height = 88.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally){
Box(modifier = Modifier
.padding(top = 8.dp)
.size(width = 55.dp, height = 55.dp)
.shadow(
spotColor = Color.White,
ambientColor = Color(0f, 0f, 0f, 0.4f),
elevation = 12.dp,
),
contentAlignment = Alignment.Center,
){
Image(
modifier = Modifier
.size(width = 24.dp, height = 24.dp),
painter = painterResource(id = drawableId),
contentDescription = ""
)
}
Text(
modifier = Modifier.padding(top = 8.dp),
text = displayText,
fontSize = 12.sp, style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}
}
@Composable
fun MessageNotification(){
Row(modifier = Modifier
.fillMaxWidth()
.height(88.dp)
.padding(start = 22.dp, top = 20.dp, bottom = 20.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically){
Box(modifier = Modifier
.size(width = 48.dp, height = 48.dp)
.border(width = 1.dp, Color(0f, 0f, 0f, 0.1f), RoundedCornerShape(2.dp)),
contentAlignment = Alignment.Center){
Icon(
modifier = Modifier
.size(width = 24.dp, height = 24.dp),
painter = painterResource(R.drawable.rider_pro_notification),
contentDescription = ""
)
}
Text(text = "NOTIFICATIONS", fontSize = 18.sp, modifier = Modifier.padding(start = 12.dp), style = TextStyle(fontWeight = FontWeight.Bold))
Box(modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.CenterEnd){
Box(modifier = Modifier
.height(18.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.Red),
contentAlignment = Alignment.Center){
Text(text = "18", fontSize = 10.sp, color = Color.White, modifier = Modifier.padding(start = 6.dp, end = 6.dp, top = 2.dp, bottom = 2.dp))
}
}
}
}
@Composable
fun ChatItem(chatNotificationData: ChatNotificationData){
Row(modifier = Modifier
.fillMaxWidth()
.height(88.dp)
.padding(start = 22.dp, top = 20.dp, bottom = 20.dp, end = 24.dp),
verticalAlignment = Alignment.CenterVertically){
Image(modifier = Modifier
.size(width = 48.dp, height = 48.dp)
.clip(RoundedCornerShape(2.dp)),
painter = painterResource(chatNotificationData.avatar),
contentDescription = "")
Column (
modifier = Modifier.fillMaxHeight().padding(start = 12.dp),
verticalArrangement = Arrangement.SpaceAround
){
Text(text = chatNotificationData.name, fontSize = 18.sp, style = TextStyle(fontWeight = FontWeight.Bold))
Text(text = chatNotificationData.message, fontSize = 14.sp,
color = Color(0f, 0f, 0f, 0.6f))
}
Box(modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.CenterEnd){
Column (
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceAround,
horizontalAlignment = Alignment.End
){
Text(text = chatNotificationData.time, fontSize = 12.sp,color = Color(0f, 0f, 0f, 0.4f))
Box(modifier = Modifier
.height(18.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.Red),
contentAlignment = Alignment.Center){
Text(text = chatNotificationData.unread.toString(), fontSize = 10.sp, color = Color.White, modifier = Modifier.padding(start = 6.dp, end = 6.dp, top = 2.dp, bottom = 2.dp))
}
}
}
}
}

View File

@@ -0,0 +1,271 @@
package com.aiosman.riderpro.ui.message
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Preview(showBackground = true)
@Composable
fun NotificationsScreen() {
val navController = LocalNavController.current
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
}
StatusBarMaskLayout(darkIcons = true) {
Column(
modifier = Modifier.fillMaxWidth().weight(1f)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_message_title),
contentDescription = "Back",
modifier = Modifier.width(120.dp)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
NotificationIndicator(10, R.drawable.rider_pro_like, "LIKE") {
navController.navigate("Likes")
}
NotificationIndicator(10, R.drawable.rider_pro_followers, "FOLLOWERS"){
navController.navigate("Followers")
}
NotificationIndicator(10, R.drawable.rider_pro_comments, "COMMENTS"){
navController.navigate("Comments")
}
}
HorizontalDivider(color = Color(0xFFEbEbEb), modifier = Modifier.padding(16.dp))
NotificationCounterItem(24)
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxSize()
) {
item {
repeat(20) {
MessageItem(
MessageItemData(
userName = "Onyama Limba",
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
timeAgo = "3 days ago",
profileImage = R.drawable.default_avatar
)
)
}
BottomNavigationPlaceholder()
}
}
}
}
}
@Composable
fun NotificationIndicator(
notificationCount: Int,
iconRes: Int,
label: String,
onClick: () -> Unit
) {
Box(
modifier = Modifier
) {
Box(
modifier = Modifier
.padding(16.dp)
.align(Alignment.TopCenter)
.clickable {
onClick()
}
) {
if (notificationCount > 0) {
Box(
modifier = Modifier
.background(Color(0xFFE53935), RoundedCornerShape(8.dp))
.padding(4.dp)
.align(Alignment.TopEnd)
) {
Text(
text = notificationCount.toString(),
color = Color.White,
fontSize = 10.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center)
)
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier.padding(16.dp)
) {
Image(
painter = painterResource(id = iconRes),
contentDescription = label,
modifier = Modifier.size(24.dp)
)
}
Box(
modifier = Modifier
) {
Text(label, modifier = Modifier.align(Alignment.Center))
}
}
}
}
}
@Composable
fun NotificationCounterItem(count: Int) {
Row(
modifier = Modifier.padding(vertical = 16.dp, horizontal = 32.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_notification),
contentDescription = "",
modifier = Modifier
.size(24.dp)
)
}
Spacer(modifier = Modifier.width(24.dp))
Text("NOTIFICATIONS", fontSize = 18.sp)
Spacer(modifier = Modifier.weight(1f))
if (count > 0) {
Box(
modifier = Modifier
.background(Color(0xFFE53935), RoundedCornerShape(16.dp))
.padding(horizontal = 8.dp, vertical = 2.dp)
) {
Text(
text = count.toString(),
color = Color.White,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(4.dp)
)
}
}
}
}
data class MessageItemData(
val userName: String,
val message: String,
val timeAgo: String,
val profileImage: Int
)
@Composable
fun MessageItem(messageItemData: MessageItemData) {
Row(
modifier = Modifier.padding(16.dp)
) {
Box() {
Image(
painter = painterResource(id = R.drawable.default_avatar),
contentDescription = "",
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(4.dp))
)
}
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "Onyama Limba",
fontSize = 16.sp,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
fontSize = 14.sp,
modifier = Modifier.padding(start = 16.dp),
maxLines = 1,
color = Color(0x99000000)
)
}
// Spacer(modifier = Modifier.weight(1f))
Column(
horizontalAlignment = Alignment.End
) {
Text(
text = "3 days ago",
fontSize = 14.sp,
color = Color(0x66000000)
)
Spacer(modifier = Modifier.height(6.dp))
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFE53935))
.padding(4.dp)
) {
Text(
text = "24",
color = Color.White,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}

View File

@@ -0,0 +1,241 @@
package com.aiosman.riderpro.ui.modification
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.ui.post.NewPostViewModel
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.R
import com.aiosman.riderpro.utils.Utils
@Preview
@Composable
fun EditModificationScreen() {
val model = NewPostViewModel
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFf8f8f8))
) {
Box(
modifier = Modifier.padding(vertical = 16.dp, horizontal = 18.dp)
) {
NoticeScreenHeader("Modification List")
}
LazyColumn(
modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 16.dp)
) {
items(NewPostViewModel.modificationList) { mod ->
AddModificationItem(mod) { updatedMod ->
NewPostViewModel.modificationList = NewPostViewModel.modificationList.map { existingMod ->
if (existingMod.key == updatedMod.key) updatedMod else existingMod
}.toMutableList()
}
Spacer(modifier = Modifier.height(16.dp))
}
item {
AddModificationButton {
NewPostViewModel.modificationList += Modification(
key = Utils.generateRandomString(4),
name = "",
price = "0.0"
)
}
}
}
}
}
data class Modification(
val key: String,
val name: String,
val price: String
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddModificationItem(modification: Modification, onUpdate: (Modification) -> Unit) {
var isEditPriceBottomModalVisible by remember { mutableStateOf(false) }
if (isEditPriceBottomModalVisible) {
ModalBottomSheet(
onDismissRequest = {
isEditPriceBottomModalVisible = false
},
containerColor = Color.White,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
dragHandle = {},
scrimColor = Color.Transparent,
shape = RectangleShape
) {
EditPriceBottomModal {
isEditPriceBottomModalVisible = false
onUpdate(
modification.copy(price = it)
)
}
}
}
Column {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(vertical = 13.dp, horizontal = 16.dp),
) {
if (modification.name.isEmpty()) {
Text("Please enter the name", fontSize = 14.sp, color = Color(0xFFd6d6d6))
}
BasicTextField(
value = modification.name,
onValueChange = {
onUpdate(
modification.copy(name = it)
)
},
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center)
)
}
Spacer(modifier = Modifier.height(1.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(top = 13.dp, bottom = 13.dp, start = 16.dp, end = 8.dp)
.clickable {
isEditPriceBottomModalVisible = true
}
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text("Price", fontSize = 16.sp)
Spacer(modifier = Modifier.weight(1f))
Text(modification.price, fontSize = 16.sp, color = Color(0xffda3832))
Spacer(modifier = Modifier.width(6.dp))
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_next),
contentDescription = "Edit",
modifier = Modifier.size(24.dp)
)
}
}
}
}
@Composable
fun AddModificationButton(onClick: () -> Unit = {}) {
val stroke = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.drawBehind {
drawRoundRect(color = Color(0xFFd6d6d6), style = stroke)
}
.clickable {
onClick()
}
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = "Add",
modifier = Modifier
.size(24.dp)
.align(Alignment.Center),
)
}
}
@Composable
fun EditPriceBottomModal(onOkClick: (String) -> Unit = {}) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
var text by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
// Modal content including BasicTextField
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(16.dp)
) {
Text("Price", fontSize = 16.sp)
Spacer(modifier = Modifier.width(16.dp))
BasicTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
singleLine = true,
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
// Logic to close the dialog/modal
onOkClick(text)
}
),
)
}
}

View File

@@ -0,0 +1,99 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Preview
@Composable
fun ModificationListScreen() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
}
val modifications = getModifications()
StatusBarMaskLayout {
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp)
.background(Color(0xFFF8F8F8))
) {
NoticeScreenHeader("Modification List")
Spacer(modifier = Modifier.height(8.dp))
LazyColumn(modifier = Modifier.padding(16.dp)) {
items(modifications.size) { index ->
val modification = modifications[index]
ModificationItem(name = modification.name, price = modification.price)
Spacer(modifier = Modifier.height(8.dp))
}
item {
BottomNavigationPlaceholder()
}
}
}
}
}
data class Modification(val name: String, val price: String)
fun getModifications(): List<Modification> {
return listOf(
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
Modification("Modification name", "$74.00"),
)
}
@Composable
fun ModificationItem(name: String, price: String) {
Card(
shape = RoundedCornerShape(8.dp),
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Color.White,
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = name, fontSize = 16.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(8.dp))
Text(text = price, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.Red)
}
}
}

View File

@@ -0,0 +1,14 @@
package com.aiosman.riderpro.ui.modifiers
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed {
this.clickable(indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}

View File

@@ -0,0 +1,339 @@
package com.aiosman.riderpro.ui.post
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Preview
@Composable
fun NewPostScreen() {
val model = NewPostViewModel
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(color = Color.Transparent)
}
StatusBarMaskLayout(
darkIcons = true,
) {
Column(
modifier = Modifier
.fillMaxSize()
) {
NewPostTopBar()
NewPostTextField("Share your adventure…", NewPostViewModel.textContent) {
NewPostViewModel.textContent = it
}
AddImageGrid()
AdditionalPostItem()
}
}
}
@Composable
fun NewPostTopBar() {
val navController = LocalNavController.current
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 18.dp, vertical = 10.dp)
) {
Row(
modifier = Modifier.align(Alignment.CenterStart),
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_close),
contentDescription = "Back",
modifier = Modifier.size(24.dp).clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
navController.popBackStack()
}
)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_send_post),
contentDescription = "Send",
modifier = Modifier.size(24.dp)
)
}
}
}
@Composable
fun NewPostTextField(hint: String, value: String, onValueChange: (String) -> Unit) {
Box(modifier = Modifier.fillMaxWidth()) {
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier
.fillMaxWidth()
.heightIn(200.dp)
.padding(horizontal = 18.dp, vertical = 10.dp)
)
if (value.isEmpty()) {
Text(
text = hint,
color = Color.Gray,
modifier = Modifier.padding(horizontal = 18.dp, vertical = 10.dp)
)
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AddImageGrid() {
val context = LocalContext.current
var imageUriList by remember { mutableStateOf(listOf<String>()) }
val pickImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data
if (uri != null) {
imageUriList = imageUriList + uri.toString()
}
}
}
val stroke = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
Box(
modifier = Modifier.fillMaxWidth()
) {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(18.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
imageUriList.forEach {
Image(
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = "Add Image",
modifier = Modifier
.size(110.dp)
.background(Color(0xFFFFFFFF))
.drawBehind {
drawRoundRect(color = Color(0xFF999999), style = stroke)
}
)
}
Box(
modifier = Modifier
.size(110.dp)
.background(Color(0xFFFFFFFF))
.drawBehind {
drawRoundRect(color = Color(0xFF999999), style = stroke)
}
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickImageLauncher.launch(this)
}
},
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = "Add Image",
modifier = Modifier
.size(48.dp)
.align(Alignment.Center)
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AdditionalPostItem() {
val model = NewPostViewModel
val navController = LocalNavController.current
var isShowLocationModal by remember { mutableStateOf(false) }
fun onSelectLocationClick() {
isShowLocationModal = true
}
if (isShowLocationModal) {
ModalBottomSheet(
onDismissRequest = {
isShowLocationModal = false
},
containerColor = Color.White
) {
// Sheet content
SelectLocationModal(
onClose = {
isShowLocationModal = false
}
) {
isShowLocationModal = false
NewPostViewModel.searchPlaceAddressResult = it
}
}
}
Column(
modifier = Modifier.fillMaxWidth()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 24.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onSelectLocationClick()
}
) {
NewPostViewModel.searchPlaceAddressResult?.let {
SelectedLocation(it) {
NewPostViewModel.searchPlaceAddressResult = null
}
} ?: Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_add_location),
contentDescription = "Location",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text("Add Location", color = Color(0xFF333333))
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_next),
contentDescription = "Add Location",
modifier = Modifier.size(24.dp)
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 24.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
navController.navigate("EditModification")
}
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_modification),
contentDescription = "Modification List",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text("Modification List", color = Color(0xFF333333))
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_next),
contentDescription = "Modification List",
modifier = Modifier.size(24.dp)
)
}
}
}
}
@Composable
fun SelectedLocation(
searchPlaceAddressResult: SearchPlaceAddressResult,
onRemoveLocation: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier
.weight(1f)
.padding(end = 16.dp)
) {
Text(searchPlaceAddressResult.name, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(4.dp))
Text(searchPlaceAddressResult.address, color = Color(0xFF9a9a9a))
}
Image(
painter = painterResource(id = R.drawable.rider_pro_close),
contentDescription = "Next",
modifier = Modifier
.size(24.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onRemoveLocation()
}
)
}
}
}

View File

@@ -0,0 +1,20 @@
package com.aiosman.riderpro.ui.post
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.aiosman.riderpro.ui.modification.Modification
object NewPostViewModel : ViewModel() {
var textContent by mutableStateOf("")
var searchPlaceAddressResult by mutableStateOf<SearchPlaceAddressResult?>(null)
var modificationList by mutableStateOf<List<Modification>>(listOf())
fun asNewPost() {
textContent = ""
searchPlaceAddressResult = null
modificationList = listOf()
}
}

View File

@@ -0,0 +1,538 @@
package com.aiosman.riderpro.ui.post
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import kotlinx.coroutines.launch
fun makeMockImages(): List<PostImage> {
return listOf(
PostImage(R.drawable.default_moment_img, "Image 1"),
PostImage(R.drawable.default_avatar, "Image 2"),
PostImage(R.drawable.rider_pro_moment_demo_1, "Image 3")
)
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun PostScreen(
id: String,
) {
var showCollapseContent by remember { mutableStateOf(true) }
val scrollState = rememberLazyListState()
StatusBarMaskLayout {
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = { BottomNavigationBar() }
) {
it
Column(modifier = Modifier.fillMaxSize()) {
Header()
Column(modifier = Modifier.animateContentSize()) {
AnimatedVisibility(visible = showCollapseContent) {
// collapse content
Column {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
) {
PostImageView(
id,
makeMockImages(),
)
}
PostDetails(
id,
)
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
) {
CommentsSection(scrollState) {
showCollapseContent = it
}
}
}
}
}
}
@Composable
fun Header() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_back), // Replace with your image resource
contentDescription = "Back",
modifier = Modifier
.size(32.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Image(
painter = painterResource(id = R.drawable.default_avatar), // Replace with your image resource
contentDescription = "Profile Picture",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "Diego Morata", fontWeight = FontWeight.Bold)
Box(
modifier = Modifier
.height(20.dp)
.wrapContentWidth()
.padding(start = 6.dp),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.height(18.dp),
painter = painterResource(id = R.drawable.follow_bg),
contentDescription = ""
)
Text(
text = "FOLLOW",
fontSize = 12.sp,
color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}
}
data class PostImage(
val imgRes: Int,
val description: String
)
@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class)
@Composable
fun PostImageView(
postId: String,
images: List<PostImage>,
) {
val sharedTransitionScope = LocalSharedTransitionScope.current
val animatedContentScope = LocalAnimatedContentScope.current
val pagerState = rememberPagerState(pageCount = { images.size })
with(sharedTransitionScope) {
Column {
HorizontalPager(
state = pagerState,
modifier = Modifier
.weight(1f)
.fillMaxWidth()
) { page ->
Image(
painter = painterResource(id = images[page].imgRes),
contentDescription = images[page].description,
contentScale = ContentScale.Crop,
modifier = Modifier.Companion
.sharedElement(
sharedTransitionScope.rememberSharedContentState(key = "image-$postId"),
animatedVisibilityScope = animatedContentScope
)
.fillMaxSize()
)
}
// Indicator container
Row(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
images.forEachIndexed { index, _ ->
Box(
modifier = Modifier
.size(8.dp)
.clip(CircleShape)
.background(
if (pagerState.currentPage == index) Color.Red else Color.Gray.copy(
alpha = 0.5f
)
)
.padding(4.dp)
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun PostDetails(
postId: String,
) {
val sharedTransitionScope = LocalSharedTransitionScope.current
val animatedContentScope = LocalAnimatedContentScope.current
with(sharedTransitionScope) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "By strongarming Ducati into giving him the factory seat.Marquez effectively …", fontSize = 16.sp, fontWeight = FontWeight.Bold, modifier = Modifier.Companion.sharedElement(
sharedTransitionScope.rememberSharedContentState(key = "text-$postId"),
animatedVisibilityScope = animatedContentScope
))
Text(text = "12-11 发布")
Spacer(modifier = Modifier.height(8.dp))
Text(text = "共231条评论")
}
}
}
fun MakeMockComments(): List<Comment> {
return listOf(
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = listOf(
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
)
)
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
),
Comment(
name = "Diego Morata",
comment = "这里太适合骑行了",
date = "3 days ago",
likes = 200,
replies = emptyList()
)
)
}
@Composable
fun CommentsSection(
scrollState: LazyListState = rememberLazyListState(),
onWillCollapse: (Boolean) -> Unit
) {
val items = MakeMockComments()
LazyColumn(
state = scrollState, modifier = Modifier
.padding(start = 16.dp, end = 16.dp)
.fillMaxHeight()
) {
items(items) { comment ->
CommentItem(comment)
}
}
// Detect scroll direction and update showCollapseContent
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(scrollState) {
coroutineScope.launch {
snapshotFlow { scrollState.firstVisibleItemScrollOffset }
.collect { offset ->
onWillCollapse(offset == 0)
}
}
}
}
data class Comment(
val name: String,
val comment: String,
val date: String,
val likes: Int,
val replies: List<Comment>
)
@Composable
fun CommentItem(comment: Comment) {
Column {
Row(modifier = Modifier.padding(vertical = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.default_avatar), // Replace with your image resource
contentDescription = "Comment Profile Picture",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = comment.name, fontWeight = FontWeight.Bold)
Text(text = comment.comment)
Text(text = comment.date, fontSize = 12.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.weight(1f))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Favorite, contentDescription = "Like")
}
Text(text = comment.likes.toString())
}
}
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier.padding(start = 16.dp)
) {
comment.replies.forEach { reply ->
CommentItem(reply)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomNavigationBar() {
Column(
modifier = Modifier.background(Color.White)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(Color.White)
) {
// grey round box
Box(
modifier = Modifier
.padding(8.dp)
.clip(RoundedCornerShape(16.dp))
.background(Color.Gray.copy(alpha = 0.1f))
.weight(1f)
.height(31.dp)
.padding(8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Filled.Edit, contentDescription = "Send")
Spacer(modifier = Modifier.width(8.dp))
Text(text = "说点什么", fontSize = 12.sp)
}
}
IconButton(
onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Favorite, contentDescription = "Send")
}
Text(text = "2077")
IconButton(
onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Star, contentDescription = "Send")
}
Text(text = "2077")
IconButton(
onClick = { /*TODO*/ }) {
Icon(Icons.Filled.CheckCircle, contentDescription = "Send")
}
Text(text = "2077")
}
BottomNavigationPlaceholder(
color = Color.White
)
}
}

View File

@@ -0,0 +1,222 @@
package com.aiosman.riderpro.ui.post
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
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.material3.Text
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.R
import com.google.android.gms.common.api.ApiException
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.api.net.PlacesClient
import com.google.android.libraries.places.api.net.SearchByTextRequest
data class SearchPlaceAddressResult(
val name: String,
val address: String
)
@Composable
fun SelectLocationModal(
onClose: () -> Unit,
onSelectedLocation: (SearchPlaceAddressResult) -> Unit
) {
val context = LocalContext.current
var queryString by remember { mutableStateOf("") }
var searchPlaceAddressResults by remember {
mutableStateOf<List<SearchPlaceAddressResult>>(
emptyList()
)
}
fun searchAddrWithGoogleMap(query: String) {
val placesClient: PlacesClient = Places.createClient(context)
val placeFields: List<Place.Field> =
listOf(Place.Field.ID, Place.Field.NAME, Place.Field.ADDRESS)
val request = SearchByTextRequest.newInstance(query, placeFields)
placesClient.searchByText(request)
.addOnSuccessListener { response ->
val place = response.places
searchPlaceAddressResults = place.map {
SearchPlaceAddressResult(it.name ?: "", it.address ?: "")
}
}.addOnFailureListener { exception ->
if (exception is ApiException) {
Log.e("SelectLocationModal", "Place not found: ${exception.statusCode}")
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 24.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp)
) {
Text(
"Check In",
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Center),
fontSize = 16.sp
)
Text(
"Cancel",
modifier = Modifier
.align(Alignment.CenterEnd)
.clickable {
onClose()
},
fontSize = 16.sp
)
}
LocationSearchTextInput(queryString, onQueryClick = {
searchAddrWithGoogleMap(queryString)
}) {
queryString = it
}
LazyColumn(
modifier = Modifier
.weight(1f)
.padding(top = 28.dp)
) {
item {
for (searchPlaceAddressResult in searchPlaceAddressResults) {
LocationItem(searchPlaceAddressResult) {
onSelectedLocation(searchPlaceAddressResult)
}
}
}
}
}
}
@Composable
fun LocationSearchTextInput(
value: String,
onQueryClick: () -> Unit,
onValueChange: (String) -> Unit
) {
val keyboardController = LocalSoftwareKeyboardController.current
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.clip(shape = RoundedCornerShape(16.dp))
.background(Color(0xffF5F5F5))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_search_location),
contentDescription = "Search",
modifier = Modifier
.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
if (value.isEmpty()) {
Text(
"search",
modifier = Modifier.padding(vertical = 16.dp),
color = Color(0xffA0A0A0)
)
}
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search
),
keyboardActions = KeyboardActions(
onSearch = {
onQueryClick()
// hide keyboard
keyboardController?.hide()
}
)
)
}
}
}
@Composable
fun LocationItem(
searchPlaceAddressResult: SearchPlaceAddressResult,
onLocationItemClick: () -> Unit = {}
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
.clickable {
onLocationItemClick()
}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier
.weight(1f)
.padding(end = 16.dp)
) {
Text(searchPlaceAddressResult.name, fontWeight = FontWeight.Bold)
Text(searchPlaceAddressResult.address, color = Color(0xFF9a9a9a))
}
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_next),
contentDescription = "Next",
modifier = Modifier.size(24.dp)
)
}
}
}