更改目录结构
This commit is contained in:
156
app/src/main/java/com/aiosman/riderpro/ui/Navi.kt
Normal file
156
app/src/main/java/com/aiosman/riderpro/ui/Navi.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
278
app/src/main/java/com/aiosman/riderpro/ui/gallery/Gallery.kt
Normal file
278
app/src/main/java/com/aiosman/riderpro/ui/gallery/Gallery.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
209
app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt
Normal file
209
app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
132
app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt
Normal file
132
app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt
Normal 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
228
app/src/main/java/com/aiosman/riderpro/ui/message/Message.kt
Normal file
228
app/src/main/java/com/aiosman/riderpro/ui/message/Message.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
271
app/src/main/java/com/aiosman/riderpro/ui/message/MessageList.kt
Normal file
271
app/src/main/java/com/aiosman/riderpro/ui/message/MessageList.kt
Normal 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
339
app/src/main/java/com/aiosman/riderpro/ui/post/NewPost.kt
Normal file
339
app/src/main/java/com/aiosman/riderpro/ui/post/NewPost.kt
Normal 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()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
538
app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt
Normal file
538
app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt
Normal 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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user