From 367d1c9f3a308d771a5f16cfeff28b7e533038c2 Mon Sep 17 00:00:00 2001 From: AllenTom Date: Sat, 24 Aug 2024 21:59:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/aiosman/riderpro/Const.kt | 4 +- .../aiosman/riderpro/data/MomentService.kt | 3 +- .../java/com/aiosman/riderpro/exp/Date.kt | 15 ++++ .../aiosman/riderpro/model/MomentEntity.kt | 3 +- .../com/aiosman/riderpro/test/TestDatabase.kt | 3 +- .../main/java/com/aiosman/riderpro/ui/Navi.kt | 24 +++++- .../aiosman/riderpro/ui/composables/Image.kt | 44 ++++++----- .../com/aiosman/riderpro/ui/index/Index.kt | 14 +++- .../riderpro/ui/index/tabs/moment/Moment.kt | 12 ++- .../riderpro/ui/index/tabs/profile/Profile.kt | 15 ++-- .../com/aiosman/riderpro/ui/post/NewPost.kt | 1 + .../java/com/aiosman/riderpro/ui/post/Post.kt | 76 +++++++++++++------ 12 files changed, 150 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/aiosman/riderpro/Const.kt b/app/src/main/java/com/aiosman/riderpro/Const.kt index a5c3994..1dd555b 100644 --- a/app/src/main/java/com/aiosman/riderpro/Const.kt +++ b/app/src/main/java/com/aiosman/riderpro/Const.kt @@ -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" } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt index 126ef22..f55e973 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/MomentService.kt @@ -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, diff --git a/app/src/main/java/com/aiosman/riderpro/exp/Date.kt b/app/src/main/java/com/aiosman/riderpro/exp/Date.kt index 3d7e429..09aea8e 100644 --- a/app/src/main/java/com/aiosman/riderpro/exp/Date.kt +++ b/app/src/main/java/com/aiosman/riderpro/exp/Date.kt @@ -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 @@ -22,4 +23,18 @@ fun Date.timeAgo(): String { days < 365 -> "$days days ago" 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) } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt b/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt index 074c061..f5fcb27 100644 --- a/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt +++ b/app/src/main/java/com/aiosman/riderpro/model/MomentEntity.kt @@ -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, diff --git a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt index 0fe761f..d740378 100644 --- a/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt +++ b/app/src/main/java/com/aiosman/riderpro/test/TestDatabase.kt @@ -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() @@ -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, diff --git a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt index cb94bee..8475b54 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -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) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt index 4963212..7a3e57b 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt @@ -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(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(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, diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt index c3b9d34..7941f52 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt @@ -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 diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt index 6e2328d..2d02b8f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/Moment.kt @@ -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 diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt index 9c73039..28e1cd0 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt @@ -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() ) ) }, diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/NewPost.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/NewPost.kt index b84c379..e61dabe 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/NewPost.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/NewPost.kt @@ -73,6 +73,7 @@ fun NewPostScreen() { } StatusBarMaskLayout( darkIcons = true, + modifier = Modifier.fillMaxSize().background(Color.White) ) { Column( modifier = Modifier diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt index fad5cfa..bee2db6 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt @@ -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.empty()) val commentsFlow = _commentsFlow.asStateFlow() + var postId: String = "" + + // 预加载的 moment + + var accountProfileEntity by mutableStateOf(null) + var moment by mutableStateOf(null) + var accountService: AccountService = AccountServiceImpl() + + /** + * 预加载,在跳转到 PostScreen 之前设置好内容 + */ + fun preTransit(momentEntity: MomentEntity?) { + this.postId = momentEntity?.id.toString() + this.moment = momentEntity + this._commentsFlow = MutableStateFlow>(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(null) - var moment by mutableStateOf(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( - key = "PostViewModel_$id", - factory = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): 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")