This commit is contained in:
2024-08-24 21:59:16 +08:00
parent 6c888655f5
commit 367d1c9f3a
12 changed files with 150 additions and 64 deletions

View File

@@ -2,6 +2,6 @@ package com.aiosman.riderpro
object ConstVars {
// api 地址
const val BASE_SERVER = "http://192.168.31.250:8088"
// const val BASE_SERVER = "https://8.137.22.101:8088"
// const val BASE_SERVER = "http://192.168.31.250:8088"
const val BASE_SERVER = "https://8.137.22.101:8088"
}

View File

@@ -44,13 +44,12 @@ data class Moment(
val time: String
) {
fun toMomentItem(): MomentEntity {
val avatar = "${ApiClient.BASE_SERVER}${user.avatar}"
return MomentEntity(
id = id.toInt(),
avatar = "${ApiClient.BASE_SERVER}${user.avatar}",
nickname = user.nickName,
location = "Worldwide",
time = time,
time = ApiClient.dateFromApiString(time),
followStatus = false,
momentTextContent = textContent,
momentPicture = R.drawable.default_moment_img,

View File

@@ -1,6 +1,7 @@
package com.aiosman.riderpro.exp
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import com.aiosman.riderpro.data.api.ApiClient
import java.util.Date
import java.util.Locale
@@ -23,3 +24,17 @@ fun Date.timeAgo(): String {
else -> "$years years ago"
}
}
fun Date.formatPostTime(): String {
val now = Calendar.getInstance()
val calendar = Calendar.getInstance()
calendar.time = this
val year = calendar.get(Calendar.YEAR)
var nowYear = now.get(Calendar.YEAR)
val dateFormat = if (year == nowYear) {
SimpleDateFormat("MM-dd", Locale.getDefault())
} else {
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
}
return dateFormat.format(this)
}

View File

@@ -1,6 +1,7 @@
package com.aiosman.riderpro.model
import androidx.annotation.DrawableRes
import java.util.Date
data class MomentImageEntity(
val id: Long,
@@ -14,7 +15,7 @@ data class MomentEntity(
val avatar: String,
val nickname: String,
val location: String,
val time: String,
val time: Date,
val followStatus: Boolean,
val momentTextContent: String,
@DrawableRes val momentPicture: Int,

View File

@@ -10,6 +10,7 @@ import com.google.gson.GsonBuilder
import io.github.serpro69.kfaker.faker
import java.io.File
import java.util.Calendar
import java.util.Date
object TestDatabase {
var momentData = emptyList<MomentEntity>()
@@ -120,7 +121,7 @@ object TestDatabase {
avatar = person.avatar,
nickname = person.nickName,
location = person.country,
time = "2023.02.02 11:23",
time = Date(),
followStatus = false,
momentTextContent = "By strongarming Ducati into giving him the factory seat.Marquez effectively …",
momentPicture = R.drawable.default_moment_img,

View File

@@ -5,6 +5,11 @@ import ImageViewer
import ModificationListScreen
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBars
@@ -121,7 +126,13 @@ fun NavigationController(
}
composable(
route = NavigationRoute.Post.route,
arguments = listOf(navArgument("id") { type = NavType.StringType })
arguments = listOf(navArgument("id") { type = NavType.StringType }),
enterTransition = {
fadeIn(animationSpec = tween(durationMillis = 100))
},
exitTransition = {
fadeOut(animationSpec = tween(durationMillis = 100))
}
) { backStackEntry ->
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
@@ -147,7 +158,16 @@ fun NavigationController(
composable(route = NavigationRoute.Followers.route) {
FollowerScreen()
}
composable(route = NavigationRoute.NewPost.route) {
composable(
route = NavigationRoute.NewPost.route,
enterTransition = {
fadeIn(animationSpec = tween(durationMillis = 100))
},
exitTransition = {
fadeOut(animationSpec = tween(durationMillis = 100))
}
) {
NewPostScreen()
}
composable(route = NavigationRoute.EditModification.route) {

View File

@@ -2,6 +2,7 @@ package com.aiosman.riderpro.ui.composables
import android.content.Context
import android.graphics.Bitmap
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -46,10 +47,12 @@ fun rememberImageBitmap(imageUrl: String, imageLoader: ImageLoader): Bitmap? {
@Composable
fun CustomAsyncImage(
context: Context,
imageUrl: String,
imageUrl: String?,
contentDescription: String?,
modifier: Modifier = Modifier,
blurHash: String? = null,
@DrawableRes
placeholderRes: Int? = null,
contentScale: ContentScale = ContentScale.Crop
) {
val imageLoader = getImageLoader(context)
@@ -63,30 +66,35 @@ fun CustomAsyncImage(
}
}
var bitmap by remember(imageUrl) { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(imageUrl) {
if (bitmap == null) {
val request = ImageRequest.Builder(context)
.data(imageUrl)
.crossfade(true)
.build()
val result = withContext(Dispatchers.IO) {
(imageLoader.execute(request) as? SuccessResult)?.drawable?.toBitmap()
}
bitmap = result
}
}
// var bitmap by remember(imageUrl) { mutableStateOf<Bitmap?>(null) }
//
// LaunchedEffect(imageUrl) {
// if (bitmap == null) {
// val request = ImageRequest.Builder(context)
// .data(imageUrl)
// .crossfade(3000)
// .build()
//
// val result = withContext(Dispatchers.IO) {
// (imageLoader.execute(request) as? SuccessResult)?.drawable?.toBitmap()
// }
// bitmap = result
// }
// }
AsyncImage(
model = bitmap ?: ImageRequest.Builder(context)
model = ImageRequest.Builder(context)
.data(imageUrl)
.crossfade(true)
.crossfade(200)
.apply {
if (placeholderRes != null) {
placeholder(placeholderRes)
return@apply
}
if (blurBitmap != null) {
placeholder(blurBitmap.toDrawable(context.resources))
}
}
.build(),
contentDescription = contentDescription,

View File

@@ -2,6 +2,8 @@ package com.aiosman.riderpro.ui.index
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -25,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.navigation.navOptions
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.index.tabs.add.AddPage
@@ -35,6 +38,7 @@ import com.aiosman.riderpro.ui.index.tabs.search.SearchScreen
import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
import com.aiosman.riderpro.ui.message.MessagePage
import com.aiosman.riderpro.ui.post.NewPostViewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
@@ -56,7 +60,7 @@ fun IndexScreen() {
val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
// systemUiController.setNavigationBarColor(Color.Transparent)
}
Scaffold(
bottomBar = {
@@ -73,8 +77,14 @@ fun IndexScreen() {
NavigationBarItem(
selected = isSelected,
onClick = {
if (it.route === NavigationItem.Add.route) {
navController.navigate(NavigationRoute.NewPost.route)
NewPostViewModel.asNewPost()
navController.navigate(
NavigationRoute.NewPost.route,
)
return@NavigationBarItem
}
model.tabIndex = idx

View File

@@ -65,6 +65,7 @@ import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity
import com.aiosman.riderpro.ui.NavigationRoute
@@ -76,6 +77,7 @@ import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.RelPostCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.NewPostViewModel
import com.aiosman.riderpro.ui.post.PostViewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch
@@ -160,6 +162,7 @@ fun MomentCard(
modifier = Modifier
.fillMaxWidth()
.noRippleClickable {
PostViewModel.preTransit(momentEntity)
navController.navigate("Post/${momentEntity.id}")
}
) {
@@ -327,7 +330,7 @@ fun MomentTopRowGroup(momentEntity: MomentEntity) {
verticalAlignment = Alignment.CenterVertically
) {
MomentPostLocation(momentEntity.location)
MomentPostTime(momentEntity.time)
MomentPostTime(momentEntity.time.timeAgo())
}
}
}
@@ -355,7 +358,6 @@ fun PostImageView(
.aspectRatio(1f),
) { page ->
val image = images[page]
with(sharedTransitionScope) {
// AsyncBlurImage(
// imageUrl = image.url,
// blurHash = image.blurHash ?: "",
@@ -375,10 +377,6 @@ fun PostImageView(
blurHash = image.blurHash,
contentScale = ContentScale.Crop,
modifier = Modifier
.sharedElement(
rememberSharedContentState(key = image),
animatedVisibilityScope = animatedVisibilityScope
)
.fillMaxSize()
// .noRippleClickable {
// ImageViewerViewModel.asNew(images, page)
@@ -387,7 +385,7 @@ fun PostImageView(
// )
// }
)
}
}
// Indicator container

View File

@@ -53,10 +53,12 @@ import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.exp.formatPostTime
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.PostViewModel
import kotlinx.coroutines.launch
@@ -457,13 +459,13 @@ fun UserMoment(scope: LazyListScope) {
@Composable
fun MomentPostUnit(momentEntity: MomentEntity) {
TimeGroup(momentEntity.time)
TimeGroup(momentEntity.time.formatPostTime())
ProfileMomentCard(
momentEntity.momentTextContent,
momentEntity.images[0].thumbnail,
momentEntity.likeCount.toString(),
momentEntity.commentCount.toString(),
momentId = momentEntity.id
momentEntity = momentEntity
)
}
@@ -496,7 +498,7 @@ fun ProfileMomentCard(
imageUrl: String,
like: String,
comment: String,
momentId: Int
momentEntity: MomentEntity
) {
Column(
modifier = Modifier
@@ -505,7 +507,7 @@ fun ProfileMomentCard(
.border(width = 1.dp, color = Color(0f, 0f, 0f, 0.1f), shape = RoundedCornerShape(6.dp))
) {
MomentCardTopContent(content)
MomentCardPicture(imageUrl, momentId)
MomentCardPicture(imageUrl, momentEntity = momentEntity)
MomentCardOperation(like, comment)
}
}
@@ -527,7 +529,7 @@ fun MomentCardTopContent(content: String) {
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun MomentCardPicture(imageUrl: String, momentId: Int) {
fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) {
val navController = LocalNavController.current
val sharedTransitionScope = LocalSharedTransitionScope.current
val animatedVisibilityScope = LocalAnimatedContentScope.current
@@ -544,10 +546,11 @@ fun MomentCardPicture(imageUrl: String, momentId: Int) {
.fillMaxSize()
.padding(16.dp)
.noRippleClickable {
PostViewModel.preTransit(momentEntity)
navController.navigate(
NavigationRoute.Post.route.replace(
"{id}",
momentId.toString()
momentEntity.id.toString()
)
)
},

View File

@@ -73,6 +73,7 @@ fun NewPostScreen() {
}
StatusBarMaskLayout(
darkIcons = true,
modifier = Modifier.fillMaxSize().background(Color.White)
) {
Column(
modifier = Modifier

View File

@@ -87,6 +87,7 @@ import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.exp.formatPostTime
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity
@@ -104,16 +105,29 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class PostViewModel(
val postId: String
) : ViewModel() {
object PostViewModel : ViewModel() {
var service: MomentService = TestMomentServiceImpl()
var commentService: CommentService = TestCommentServiceImpl()
var userService: UserService = TestUserServiceImpl()
private var _commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
val commentsFlow = _commentsFlow.asStateFlow()
var postId: String = ""
// 预加载的 moment
var accountProfileEntity by mutableStateOf<AccountProfileEntity?>(null)
var moment by mutableStateOf<MomentEntity?>(null)
var accountService: AccountService = AccountServiceImpl()
/**
* 预加载,在跳转到 PostScreen 之前设置好内容
*/
fun preTransit(momentEntity: MomentEntity?) {
this.postId = momentEntity?.id.toString()
this.moment = momentEntity
this._commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
this.accountProfileEntity = null
init {
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
@@ -129,10 +143,6 @@ class PostViewModel(
}
}
var accountProfileEntity by mutableStateOf<AccountProfileEntity?>(null)
var moment by mutableStateOf<MomentEntity?>(null)
var accountService: AccountService = AccountServiceImpl()
suspend fun initData() {
moment = service.getMomentById(postId.toInt())
moment?.let {
@@ -220,20 +230,35 @@ class PostViewModel(
}
}
var avatar: String? = null
get() {
accountProfileEntity?.avatar?.let {
return it
}
moment?.avatar?.let {
return it
}
return field
}
var nickname: String? = null
get() {
accountProfileEntity?.nickName?.let {
return it
}
moment?.nickname?.let {
return it
}
return field
}
}
@Composable
fun PostScreen(
id: String,
) {
val viewModel = viewModel<PostViewModel>(
key = "PostViewModel_$id",
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PostViewModel(id) as T
}
}
)
val viewModel = PostViewModel
val scope = rememberCoroutineScope()
val commentsPagging = viewModel.commentsFlow.collectAsLazyPagingItems()
@@ -283,8 +308,8 @@ fun PostScreen(
.fillMaxSize()
) {
Header(
avatar = viewModel.moment?.avatar,
nickname = viewModel.moment?.nickname,
avatar = viewModel.avatar,
nickname = viewModel.nickname,
userId = viewModel.moment?.authorId,
isFollowing = viewModel.accountProfileEntity?.isFollowing ?: false,
onFollowClick = {
@@ -373,10 +398,15 @@ fun Header(
.size(32.dp)
)
Spacer(modifier = Modifier.width(8.dp))
avatar?.let {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color.Gray.copy(alpha = 0.1f))
) {
CustomAsyncImage(
context,
it,
avatar,
contentDescription = "Profile Picture",
modifier = Modifier
.size(40.dp)
@@ -395,7 +425,6 @@ fun Header(
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(text = nickname ?: "", fontWeight = FontWeight.Bold)
Box(
@@ -441,7 +470,8 @@ fun PostImageView(
state = pagerState,
modifier = Modifier
.weight(1f)
.fillMaxWidth().background(Color.Gray.copy(alpha = 0.1f)),
.fillMaxWidth()
.background(Color.Gray.copy(alpha = 0.1f)),
) { page ->
val image = images[page]
with(sharedTransitionScope) {
@@ -514,7 +544,7 @@ fun PostDetails(
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
)
Text(text = "12-11 发布")
Text(text = "${momentEntity?.time?.formatPostTime()} 发布")
Spacer(modifier = Modifier.height(8.dp))
Text(text = "${momentEntity?.commentCount ?: 0} Comments")