diff --git a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt index d0328026..1f6eed61 100644 --- a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt +++ b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt @@ -64,6 +64,7 @@ import com.whyranoid.domain.usecase.GetTopRankChallengePreviewsUseCase import com.whyranoid.domain.usecase.GetUserBadgesUseCase import com.whyranoid.domain.usecase.GetUserDetailUseCase import com.whyranoid.domain.usecase.GetUserPostPreviewsUseCase +import com.whyranoid.domain.usecase.GetUserPostsUseCase import com.whyranoid.domain.usecase.GetUserUseCase import com.whyranoid.domain.usecase.LikePostUseCase import com.whyranoid.domain.usecase.RequestLoginUseCase @@ -98,6 +99,7 @@ import com.whyranoid.presentation.viewmodel.SignInViewModel import com.whyranoid.presentation.viewmodel.SplashViewModel import com.whyranoid.presentation.viewmodel.TotalBadgeViewModel import com.whyranoid.presentation.viewmodel.UserPageViewModel +import com.whyranoid.presentation.viewmodel.UserPostsViewModel import com.whyranoid.presentation.viewmodel.challenge.ChallengeDetailViewModel import com.whyranoid.presentation.viewmodel.challenge.ChallengeExitViewModel import com.whyranoid.presentation.viewmodel.challenge.ChallengeMainViewModel @@ -132,6 +134,7 @@ val viewModelModule = viewModel { FollowingViewModel(get(), get(), get(), get(), get(), get()) } viewModel { SettingViewModel(get(), get()) } viewModel { TotalBadgeViewModel(get(), get()) } + viewModel { UserPostsViewModel(get(), get()) } } val repositoryModule = @@ -198,6 +201,7 @@ val useCaseModule = single { SendCommentUseCase(get(), get()) } single { GetUserUseCase(get()) } single { ChangeChallengeStatusUseCase(get(), get())} + single { GetUserPostsUseCase(get(), get()) } } val databaseModule = diff --git a/data/src/main/java/com/whyranoid/data/API.kt b/data/src/main/java/com/whyranoid/data/API.kt index 844951b8..a900d3a6 100644 --- a/data/src/main/java/com/whyranoid/data/API.kt +++ b/data/src/main/java/com/whyranoid/data/API.kt @@ -19,10 +19,14 @@ object API { const val UPLOAD_POST = "api/community/upload-post" + // 사용자가 작성한 게시글을 가져온다 const val LIST_UP_MY_POST = "api/walkies/listup-my-post" + // 사용자의 팔로잉들의 게시글을 가져온다 const val LIST_UP_POST = "api/community/listup-post" + const val LIST_UP_EVERY_POST = "api/community/listup-every-post" + const val LIST_UP_COMMENT = "api/community/listup-comment" const val SEARCH = "api/community/search-nickname" diff --git a/data/src/main/java/com/whyranoid/data/datasource/post/PostDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/datasource/post/PostDataSourceImpl.kt index 38bd1338..70af74b9 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/post/PostDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/post/PostDataSourceImpl.kt @@ -44,6 +44,12 @@ class PostDataSourceImpl(private val postService: PostService) : PostDataSource return Result.success(response.body()?.map { it.toPostPreview() }!!) } + override suspend fun getMyPosts(uid: Long, myUid: Long): Result> { + val response = postService.myPosts(uid) + response.body() ?: return Result.failure(Throwable(response.message().toString())) + return Result.success(response.body()?.map { it.toPost(myUid) }!!) + } + override suspend fun getMyPostPreviews( uid: Long, year: Int, @@ -101,6 +107,13 @@ class PostDataSourceImpl(private val postService: PostService) : PostDataSource } } + override suspend fun getEveryPost(uid: Long): Result> { + return kotlin.runCatching { + val posts = requireNotNull(postService.getPosts(uid).body()) + posts.map { it.toPost(uid) } + } + } + override suspend fun getComments(postId: Long): Result> { return kotlin.runCatching { val comments = requireNotNull(postService.getComments(postId).body()) diff --git a/data/src/main/java/com/whyranoid/data/datasource/post/PostService.kt b/data/src/main/java/com/whyranoid/data/datasource/post/PostService.kt index c677c9f7..e890c648 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/post/PostService.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/post/PostService.kt @@ -27,16 +27,23 @@ interface PostService { @Part image: MultipartBody.Part, ): Response + // 사용자가 작성한 글을 가져온다 @GET(API.LIST_UP_MY_POST) suspend fun myPosts( @Query("walkieId") uid: Long, ): Response> + // 사용자의 팔로잉의 글을 가져온다 @GET(API.LIST_UP_POST) suspend fun getPosts( @Query("walkieId") uid: Long, ): Response> + @GET(API.LIST_UP_EVERY_POST) + suspend fun getEveryPosts( + @Query("walkieId") uid: Long, + ): Response> + @GET(API.LIST_UP_COMMENT) suspend fun getComments( @Query("postId") postId: Long, diff --git a/data/src/main/java/com/whyranoid/data/model/post/PostResponse.kt b/data/src/main/java/com/whyranoid/data/model/post/PostResponse.kt index 50927a3c..b5078514 100644 --- a/data/src/main/java/com/whyranoid/data/model/post/PostResponse.kt +++ b/data/src/main/java/com/whyranoid/data/model/post/PostResponse.kt @@ -24,7 +24,7 @@ data class PostResponse( val destructedHistoryContent = historyContent.split('_') return PostPreview( author = poster.toUser(), - id = this.poster.uid, + id = this.postId, isLiked = this.liked, likers = this.likers.map { it.toUser() }, imageUrl = this.photo, diff --git a/data/src/main/java/com/whyranoid/data/repository/PostRepositoryImpl.kt b/data/src/main/java/com/whyranoid/data/repository/PostRepositoryImpl.kt index 8d1311b8..213f2a64 100644 --- a/data/src/main/java/com/whyranoid/data/repository/PostRepositoryImpl.kt +++ b/data/src/main/java/com/whyranoid/data/repository/PostRepositoryImpl.kt @@ -35,6 +35,10 @@ class PostRepositoryImpl( return postDataSource.getMyPostPreviews(uid, year, month, day) } + override suspend fun getMyPosts(uid: Long, myUid: Long): Result> { + return postDataSource.getMyPosts(uid, myUid) + } + override suspend fun getPost(postId: Long): Result { return postDataSource.getPost(postId) } @@ -53,6 +57,10 @@ class PostRepositoryImpl( return postDataSource.getMyFollowingsPost(uid) } + override suspend fun getEveryPost(uid: Long): Result> { + return postDataSource.getMyFollowingsPost(uid) + } + override suspend fun getComments(postId: Long): Result> { return postDataSource.getComments(postId) } diff --git a/domain/src/main/java/com/whyranoid/domain/datasource/PostDataSource.kt b/domain/src/main/java/com/whyranoid/domain/datasource/PostDataSource.kt index 9ea5e02b..fe2c89f0 100644 --- a/domain/src/main/java/com/whyranoid/domain/datasource/PostDataSource.kt +++ b/domain/src/main/java/com/whyranoid/domain/datasource/PostDataSource.kt @@ -5,10 +5,31 @@ import com.whyranoid.domain.model.post.Post import com.whyranoid.domain.model.post.PostPreview interface PostDataSource { + + /** + * 사용자의 팔로잉들의 글을 가져온다 + * + * @param uid + * @return + */ suspend fun getPostPreviews(uid: Long): Result> + /** + * 해당 사용자가 작성한 글들을 가져온다 + * + * @param uid + * @return + */ suspend fun getMyPostPreviews(uid: Long): Result> + /** + * 해당 사용자가 작성한 글들을 가져온다 + * + * @param uid + * @return + */ + suspend fun getMyPosts(uid: Long, myUid: Long): Result> + suspend fun getPostPreviews( uid: Long, year: Int, @@ -35,6 +56,8 @@ interface PostDataSource { suspend fun getMyFollowingsPost(uid: Long): Result> + suspend fun getEveryPost(uid: Long): Result> + suspend fun getComments(postId: Long): Result> suspend fun sendComment( diff --git a/domain/src/main/java/com/whyranoid/domain/model/user/UserDetail.kt b/domain/src/main/java/com/whyranoid/domain/model/user/UserDetail.kt index a8fc8ee7..dd09cb2c 100644 --- a/domain/src/main/java/com/whyranoid/domain/model/user/UserDetail.kt +++ b/domain/src/main/java/com/whyranoid/domain/model/user/UserDetail.kt @@ -4,7 +4,7 @@ import com.whyranoid.domain.model.post.PostPreview data class UserDetail( val user: User, - val postCount: Int, + val postCount: Int?, val followerCount: Int, val followingCount: Int, val isFollowing: Boolean? = null, diff --git a/domain/src/main/java/com/whyranoid/domain/repository/PostRepository.kt b/domain/src/main/java/com/whyranoid/domain/repository/PostRepository.kt index 5f14b8b0..760607a2 100644 --- a/domain/src/main/java/com/whyranoid/domain/repository/PostRepository.kt +++ b/domain/src/main/java/com/whyranoid/domain/repository/PostRepository.kt @@ -5,10 +5,30 @@ import com.whyranoid.domain.model.post.Post import com.whyranoid.domain.model.post.PostPreview interface PostRepository { + + /** + * 유저의 팔로잉들이 작성한 글을 가져옴 + * + * @param uid + * @return + */ suspend fun getUserPostPreviews(uid: Long): Result> + + /** + * 해당 유저가 작성한 글을 가져옴 + * + * @param uid + * @return + */ suspend fun getMyPostPreviews(uid: Long): Result> + /** + * 유저의 팔로잉들이 작성한 글을 가져옴 + * + * @param uid + * @return + */ suspend fun getUserPostPreviews( uid: Long, year: Int, @@ -16,6 +36,12 @@ interface PostRepository { day: Int, ): Result> + /** + * 해당 유저가 작성한 글을 가져옴 + * + * @param uid + * @return + */ suspend fun getMyPostPreviews( uid: Long, year: Int, @@ -23,6 +49,17 @@ interface PostRepository { day: Int, ): Result> + /** + * 해당 유저가 작성한 글을 가져옴 + * + * @param uid + * @return + */ + suspend fun getMyPosts( + uid: Long, + myUid: Long + ): Result> + suspend fun getPost(postId: Long): Result suspend fun uploadPost( @@ -35,6 +72,8 @@ interface PostRepository { suspend fun getMyFollowingsPost(uid: Long): Result> + suspend fun getEveryPost(uid: Long): Result> + suspend fun getComments(postId: Long): Result> suspend fun sendComment( diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt index 4132f493..c1e30906 100644 --- a/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt +++ b/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt @@ -9,7 +9,7 @@ class GetFollowingsPostsUseCase( private val accountRepository: AccountRepository, private val postRepository: PostRepository, ) { - suspend operator fun invoke(): Result> { + suspend operator fun invoke(isEveryPost: Boolean): Result> { val myUid = requireNotNull(accountRepository.walkieId.first()) return postRepository.getMyFollowingsPost(myUid) } diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/GetUserPostsUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/GetUserPostsUseCase.kt new file mode 100644 index 00000000..46ff3877 --- /dev/null +++ b/domain/src/main/java/com/whyranoid/domain/usecase/GetUserPostsUseCase.kt @@ -0,0 +1,14 @@ +package com.whyranoid.domain.usecase + +import com.whyranoid.domain.model.post.Post +import com.whyranoid.domain.repository.AccountRepository +import com.whyranoid.domain.repository.PostRepository + +class GetUserPostsUseCase( + private val accountRepository: AccountRepository, + private val postRepository: PostRepository, +) { + suspend operator fun invoke(uid: Long): Result> { + return postRepository.getMyPosts(uid, accountRepository.getUID()) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/component/community/PostItem.kt b/presentation/src/main/java/com/whyranoid/presentation/component/community/PostItem.kt index fa38e1bc..9033d437 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/component/community/PostItem.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/component/community/PostItem.kt @@ -8,9 +8,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.material3.Divider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage import com.whyranoid.domain.model.post.Post import com.whyranoid.domain.model.post.toPostPreview import com.whyranoid.domain.model.user.User @@ -22,6 +20,7 @@ fun PostItem( onLikeClicked: (Long) -> Unit = {}, onProfileClicked: (User) -> Unit = {}, onCommentClicked: (Post) -> Unit = {}, + onPostPreviewClicked: (uid: Long, postId: Long) -> Unit = { _, _ -> } ) { Column( Modifier.fillMaxHeight(), @@ -42,7 +41,8 @@ fun PostItem( postPreview = post.toPostPreview(), modifier = Modifier .fillMaxWidth() - .aspectRatio(1f) + .aspectRatio(1f), + onPostPreviewClicked = onPostPreviewClicked ) PostContentItem( diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt index 85e58c13..e40e834b 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt @@ -34,6 +34,7 @@ import com.whyranoid.presentation.screens.challenge.ChallengeExitScreen import com.whyranoid.presentation.screens.challenge.ChallengeMainScreen import com.whyranoid.presentation.screens.community.CommentScreen import com.whyranoid.presentation.screens.community.SearchFriendScreen +import com.whyranoid.presentation.screens.community.UserPostScreen import com.whyranoid.presentation.screens.mypage.MyPageScreen import com.whyranoid.presentation.screens.mypage.TotalBadgeScreen import com.whyranoid.presentation.screens.mypage.UserPageScreen @@ -144,10 +145,6 @@ fun AppScreenContent( MyPageScreen(navController) } - composable(Screen.MyPage.route) { - MyPageScreen(navController) - } - composable(Screen.TotalBadgeScreen.route) { TotalBadgeScreen(navController) } @@ -169,6 +166,7 @@ fun AppScreenContent( val isChallenging = arguments.getBoolean("isChallenging") ChallengeDetailScreen(navController, challengeId, isChallenging) } + composable( Screen.ChallengeExitScreen.route, Screen.ChallengeExitScreen.arguments, @@ -213,6 +211,12 @@ fun AppScreenContent( post?.let { CommentScreen( post = it, + onProfileClicked = { uid, nickname -> + navController.navigate("userpage/${uid}/${nickname}/false") + }, + onMyProfileClicked = { + navController.navigate(Screen.MyPage.route) + }, onBackClicked = { navController.popBackStack() }, ) } @@ -221,6 +225,29 @@ fun AppScreenContent( composable(Screen.SettingScreen.route) { SettingsScreen(navHostController = navController) } + + composable( + Screen.UserPostsScreen.route, + Screen.UserPostsScreen.arguments, + ) { backStackEntry -> + val arguments = requireNotNull(backStackEntry.arguments) + val uid = arguments.getLong(Screen.UID_ARGUMENT) + val postId = arguments.getLong(Screen.POST_ID) + UserPostScreen( + uid, + postId, + onProfileClicked = { user -> + navController.navigate("userPage/${user.uid}/${user.nickname}/false") + }, + onCommentClicked = { post -> + navController.currentBackStackEntry?.savedStateHandle?.set( + "post", + post + ) + navController.navigate(Screen.CommentScreen.route) + } + ) + } } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt index 2c90df1b..1fafc57a 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt @@ -1,35 +1,41 @@ package com.whyranoid.presentation.screens +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.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.Search import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.whyranoid.presentation.component.bar.WalkieTopBar import com.whyranoid.presentation.component.community.PostItem import com.whyranoid.presentation.component.running.RunningFollowerItemWithLikable +import com.whyranoid.presentation.theme.WalkieColor import com.whyranoid.presentation.theme.WalkieTypography import com.whyranoid.presentation.viewmodel.CommunityScreenViewModel import org.koin.androidx.compose.koinViewModel @@ -45,7 +51,7 @@ fun CommunityScreen(navController: NavController) { topBar = { WalkieTopBar( leftContent = { - Row { + Row(verticalAlignment = Alignment.CenterVertically) { Text( text = "커뮤니티", style = WalkieTypography.Title.copy(fontWeight = FontWeight(600)), @@ -53,14 +59,25 @@ fun CommunityScreen(navController: NavController) { Spacer(modifier = Modifier.width(7.dp)) - Icon( - modifier = - Modifier - .clickable { - }, - imageVector = Icons.Filled.KeyboardArrowDown, - contentDescription = "Down Arrow", - ) + val isEveryPost = state.isEveryPost.getDataOrNull() ?: true + + Box( + modifier = Modifier + .width(48.dp) + .height(24.dp) + .clip(RoundedCornerShape(10.dp)) + .background(if (isEveryPost) WalkieColor.GrayBackground else WalkieColor.PrimarySurface) + .clickable { viewModel.switchPostType() }, + contentAlignment = Alignment.Center + ){ + Text( + text = "팔로잉", + fontSize = 14.sp, + color = if (isEveryPost) WalkieColor.GrayBorder else WalkieColor.Primary, + fontWeight = if (isEveryPost) FontWeight.Thin else FontWeight.SemiBold, + letterSpacing = if (isEveryPost) 0.sp else (-0.7).sp + ) + } } }, rightContent = { @@ -136,6 +153,9 @@ fun CommunityScreen(navController: NavController) { ) navController.navigate(Screen.CommentScreen.route) }, + onPostPreviewClicked = { uid: Long, postId: Long -> + navController.navigate(Screen.UserPostsScreen.route(uid, postId)) + } ) } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/Screen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/Screen.kt index 19081061..ade74262 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/Screen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/Screen.kt @@ -132,6 +132,17 @@ sealed class Screen( "settingScreen", ) + object UserPostsScreen : Screen( + route = "userPostsScreen/{$UID_ARGUMENT}/{$POST_ID}", + arguments = + listOf( + navArgument(UID_ARGUMENT) { type = NavType.LongType }, + navArgument(POST_ID) { type = NavType.LongType }, + ) + ) { + fun route(uid: Long, postId: Long) = "userPostsScreen/$uid/$postId" + } + companion object { val bottomNavigationItems = listOf(Running, Community, ChallengeMainScreen, MyPage) diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/community/CommentScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/community/CommentScreen.kt index 50e34fa9..eac53cd5 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/community/CommentScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/community/CommentScreen.kt @@ -1,6 +1,5 @@ package com.whyranoid.presentation.screens.community -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -50,6 +49,7 @@ import coil.compose.AsyncImage import com.whyranoid.domain.model.post.Comment import com.whyranoid.domain.model.post.Post import com.whyranoid.domain.model.user.User +import com.whyranoid.domain.repository.AccountRepository import com.whyranoid.domain.repository.PostRepository import com.whyranoid.domain.repository.UserRepository import com.whyranoid.domain.usecase.GetMyUidUseCase @@ -66,7 +66,8 @@ import org.koin.androidx.compose.get fun CommentScreen( modifier: Modifier = Modifier, post: Post, - onProfileClicked: (uid: Long) -> Unit = { }, + onProfileClicked: (uid: Long, nickname: String) -> Unit = { _, _ -> }, + onMyProfileClicked: () -> Unit = { }, onBackClicked: () -> Unit = { }, postRepo: PostRepository = get(), userRepo: UserRepository = get(), @@ -82,7 +83,6 @@ fun CommentScreen( LaunchedEffect(post.id, isProgress) { postRepo.getComments(post.id).onSuccess { comments = it.reversed() - Log.d("ju0828", it.toString()) } myUid().onSuccess { uid -> @@ -102,9 +102,9 @@ fun CommentScreen( ) { Icon( modifier = - Modifier - .align(Alignment.CenterStart) - .clickable { onBackClicked() }, + Modifier + .align(Alignment.CenterStart) + .clickable { onBackClicked() }, imageVector = Icons.Filled.KeyboardArrowLeft, contentDescription = "Left Arrow", ) @@ -137,33 +137,34 @@ fun CommentScreen( ) { paddingValues -> LazyColumn( modifier = - Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(horizontal = 16.dp) - .padding(top = 16.dp) - .padding(paddingValues), + Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 16.dp) + .padding(top = 16.dp) + .padding(paddingValues), verticalArrangement = Arrangement.spacedBy(8.dp), ) { item { PostComment( modifier = - Modifier - .padding(horizontal = 20.dp), + Modifier + .padding(horizontal = 20.dp), post.author.uid, post.author.imageUrl, post.author.nickname, post.contents, onProfileClicked, + onMyProfileClicked, ) Spacer( modifier = - Modifier - .padding(top = 16.dp) - .height(1.dp) - .fillMaxWidth() - .background(WalkieColor.GrayDisable), + Modifier + .padding(top = 16.dp) + .height(1.dp) + .fillMaxWidth() + .background(WalkieColor.GrayDisable), ) } @@ -171,14 +172,15 @@ fun CommentScreen( items(list.size) { index -> PostComment( modifier = - Modifier - .padding(horizontal = 20.dp) - .padding(top = 8.dp), + Modifier + .padding(horizontal = 20.dp) + .padding(top = 8.dp), list[index].commenterId, list[index].commenter.imageUrl, list[index].commenter.nickname, list[index].content, onProfileClicked, + onMyProfileClicked, ) } } @@ -186,7 +188,9 @@ fun CommentScreen( } Box( - modifier = Modifier.fillMaxSize().imePadding(), + modifier = Modifier + .fillMaxSize() + .imePadding(), contentAlignment = Alignment.Center, ) { if (isProgress) CircularProgressIndicator() @@ -200,8 +204,11 @@ fun PostComment( imageUrl: String, nickname: String, content: String, - onProfileClicked: (uid: Long) -> Unit = { }, + onProfileClicked: (uid: Long, nickname: String) -> Unit = { _, _ -> }, + onMyProfileClicked: () -> Unit, + accountRepository: AccountRepository = get(), ) { + val scope = rememberCoroutineScope() Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically, @@ -210,10 +217,15 @@ fun PostComment( model = imageUrl.ifBlank { User.DUMMY.imageUrl }, contentDescription = "user image", modifier = - Modifier - .size(34.dp) - .clip(RoundedCornerShape(50.dp)) - .clickable { onProfileClicked(uid) }, + Modifier + .size(34.dp) + .clip(RoundedCornerShape(50.dp)) + .clickable { + scope.launch { + if (uid != accountRepository.getUID()) onProfileClicked(uid, nickname) + else onMyProfileClicked() + } + }, contentScale = ContentScale.Crop, ) @@ -228,7 +240,14 @@ fun PostComment( } Text( - modifier = Modifier.padding(start = 8.dp), + modifier = Modifier + .padding(start = 8.dp) + .clickable { + scope.launch { + if (uid != accountRepository.getUID()) onProfileClicked(uid, nickname) + else onMyProfileClicked() + } + }, text = text, style = WalkieTypography.Body2, ) @@ -244,9 +263,16 @@ fun BottomTextField( var textFieldValue by remember { mutableStateOf(TextFieldValue("")) } Column(modifier) { - Spacer(modifier = Modifier.background(WalkieColor.GrayDisable).fillMaxWidth().height(1.dp)) + Spacer( + modifier = Modifier + .background(WalkieColor.GrayDisable) + .fillMaxWidth() + .height(1.dp) + ) Row( - modifier = Modifier.fillMaxWidth().height(50.dp), + modifier = Modifier + .fillMaxWidth() + .height(50.dp), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically, ) { @@ -262,25 +288,32 @@ fun BottomTextField( ).forEach { Text( modifier = - Modifier.clip(RoundedCornerShape(6.dp)) - .clickable { - val newCursorPosition = textFieldValue.text.length + 2 - textFieldValue = - TextFieldValue(textFieldValue.text + it).copy( - selection = TextRange(newCursorPosition), - ) - }.padding(6.dp), + Modifier + .clip(RoundedCornerShape(6.dp)) + .clickable { + val newCursorPosition = textFieldValue.text.length + 2 + textFieldValue = + TextFieldValue(textFieldValue.text + it).copy( + selection = TextRange(newCursorPosition), + ) + } + .padding(6.dp), fontSize = 20.sp, text = it, ) } } - Spacer(modifier = Modifier.background(WalkieColor.GrayDisable).fillMaxWidth().height(1.dp)) + Spacer( + modifier = Modifier + .background(WalkieColor.GrayDisable) + .fillMaxWidth() + .height(1.dp) + ) Box( modifier = - Modifier - .fillMaxWidth() - .wrapContentHeight(), + Modifier + .fillMaxWidth() + .wrapContentHeight(), contentAlignment = Alignment.CenterStart, ) { if (textFieldValue.text.isEmpty()) { @@ -288,10 +321,10 @@ fun BottomTextField( modifier = Modifier.padding(start = 60.dp), text = "댓글 달기", style = - LocalTextStyle.current.copy( - fontSize = 16.sp, - color = WalkieColor.GrayDefault, - ), + LocalTextStyle.current.copy( + fontSize = 16.sp, + color = WalkieColor.GrayDefault, + ), ) } @@ -299,29 +332,31 @@ fun BottomTextField( model = imageUrl.ifBlank { User.DUMMY.imageUrl }, contentDescription = "user image", modifier = - Modifier - .padding(horizontal = 12.dp, vertical = 6.dp) - .align(Alignment.TopStart) - .size(34.dp) - .clip(RoundedCornerShape(50.dp)), + Modifier + .padding(horizontal = 12.dp, vertical = 6.dp) + .align(Alignment.TopStart) + .size(34.dp) + .clip(RoundedCornerShape(50.dp)), contentScale = ContentScale.Crop, ) Text( modifier = - Modifier.clickable { + Modifier + .clickable { if (textFieldValue.text.isNotBlank()) { onSendClicked(textFieldValue.text) textFieldValue = TextFieldValue("") } - }.padding(12.dp) - .align(Alignment.TopEnd), + } + .padding(12.dp) + .align(Alignment.TopEnd), text = "게시", style = - LocalTextStyle.current.copy( - fontSize = 16.sp, - color = if (textFieldValue.text.isNotBlank()) WalkieColor.Primary else WalkieColor.GrayDisable, - ), + LocalTextStyle.current.copy( + fontSize = 16.sp, + color = if (textFieldValue.text.isNotBlank()) WalkieColor.Primary else WalkieColor.GrayDisable, + ), ) BasicTextField( @@ -329,7 +364,7 @@ fun BottomTextField( value = textFieldValue, onValueChange = { textFieldValue = it }, textStyle = - LocalTextStyle.current.copy(fontSize = 16.sp), + LocalTextStyle.current.copy(fontSize = 16.sp), decorationBox = { innerTextField -> Column { Row( diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/community/UserPostsScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/community/UserPostsScreen.kt index 52f7978c..73758ad2 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/community/UserPostsScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/community/UserPostsScreen.kt @@ -1,36 +1,90 @@ package com.whyranoid.presentation.screens.community +import android.util.Log +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.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowLeft +import androidx.compose.material3.Icon 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.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.whyranoid.domain.model.post.Post import com.whyranoid.domain.model.user.User import com.whyranoid.presentation.component.community.PostItem import com.whyranoid.presentation.theme.WalkieTheme import com.whyranoid.presentation.theme.WalkieTypography import com.whyranoid.presentation.viewmodel.CommunityScreenViewModel +import com.whyranoid.presentation.viewmodel.UserPostUiState +import com.whyranoid.presentation.viewmodel.UserPostsViewModel import org.koin.androidx.compose.koinViewModel @Composable fun UserPostScreen( - user: User, - postPreviews: List, - startIndex: Int, + uid: Long, + postId: Long, + onProfileClicked: (user: User) -> Unit, + onCommentClicked: (post: Post) -> Unit, onBackPressed: () -> Unit = {}, - ) { - val viewModel = koinViewModel() + val viewModel = koinViewModel() + val communityViewModel = koinViewModel() + val userPostUiState = viewModel.userPostUiState.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = uid) { + viewModel.getUiData(uid) + } + + when (userPostUiState.value) { + UserPostUiState.Loading -> { + Log.d("ju0828", "Loading") + /* todo progressbar*/ + } + + UserPostUiState.Failed -> { + Log.d("ju0828", "Failed") + /* todo progressbar */ + } + + is UserPostUiState.Success -> { + val uiData = (userPostUiState.value as UserPostUiState.Success) + UserPostUi( + uiData.user, + uiData.posts, + postId, + communityViewModel::likePost, + onProfileClicked, + onCommentClicked, + onBackPressed + ) + } + } + +} +@Composable +fun UserPostUi( + user: User, + posts: List, + postId: Long, + onClickLikePost: (postId: Long) -> Unit, + onProfileClicked: (user: User) -> Unit, + onCommentClicked: (post: Post) -> Unit, + onBackPressed: () -> Unit +) { Column( modifier = Modifier .fillMaxWidth() @@ -40,13 +94,23 @@ fun UserPostScreen( Column( Modifier .fillMaxWidth() - .padding(horizontal = 20.dp) .padding(top = 20.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Box( + modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterStart, ) { + Icon( + modifier = + Modifier + .padding(start = 12.dp) + .padding(bottom = 12.dp) + .align(Alignment.CenterStart) + .clickable { onBackPressed() }, + imageVector = Icons.Filled.KeyboardArrowLeft, + contentDescription = "Left Arrow", + ) Column( modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally, @@ -54,38 +118,46 @@ fun UserPostScreen( Text( style = WalkieTypography.Body1_Normal, text = user.nickname, - modifier = Modifier - .padding(bottom = 24.dp), ) Text( style = WalkieTypography.Title, text = "게시물", modifier = Modifier - .padding(bottom = 24.dp), + .padding(bottom = 12.dp), ) } } - Column { - postPreviews.forEach { post -> - PostItem( - post = post, - onLikeClicked = { postId -> - viewModel.likePost(postId) - }, - onProfileClicked = { user -> - // navController.back - }, - onCommentClicked = { post -> -// navController.currentBackStackEntry?.savedStateHandle?.set( -// "post", -// post -// ) -// navController.navigate(Screen.CommentScreen.route) - }, - ) + val scrollState = rememberLazyListState() + LaunchedEffect(key1 = postId) { + scrollState.scrollToItem( + maxOf(0, posts.indexOfFirst { it.id == postId }), + ) + } + LazyColumn( + state = scrollState, + ) { + posts.forEach { post -> + item { + PostItem( + post = post, + onLikeClicked = { postId -> + onClickLikePost(postId) + }, + onProfileClicked = { clickedUser -> + if (clickedUser.uid == user.uid) { + onBackPressed() + } else { + onProfileClicked(user) + } + }, + onCommentClicked = { post -> + onCommentClicked(post) + }, + ) + } } } } @@ -96,6 +168,6 @@ fun UserPostScreen( @Preview fun UserPostsScreenPreview() { WalkieTheme { - UserPostScreen(User.DUMMY, listOf(Post.DUMMY), 0) + UserPostScreen(User.DUMMY.uid, 0, {}, {}, {}) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt index 11ea8e81..8b7c5f00 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/MyPageScreen.kt @@ -101,6 +101,9 @@ fun MyPageScreen( onTotalBadgePageClicked = { navController.navigate(Screen.TotalBadgeScreen.route) }, + onPostPreviewClicked = { uid, postId: Long -> + navController.navigate(Screen.UserPostsScreen.route(uid, postId)) + }, onPostCreateClicked = { navController.navigate(Screen.AddPostScreen.route) }, @@ -162,7 +165,7 @@ fun UserPageContent( nickname: String? = null, // 상대방 페이지인 경우에 존재, 마이페이지일 경우 null state: UserPageState, onTotalBadgePageClicked: () -> Unit = {}, - onPostPreviewClicked: (id: Long) -> Unit = {}, + onPostPreviewClicked: (uid: Long, postId: Long) -> Unit = { _, _ ->}, onPostCreateClicked: () -> Unit = {}, onProfileEditClicked: () -> Unit = {}, onSettingsClicked: () -> Unit = {}, @@ -225,7 +228,7 @@ fun UserPageContent( ) { TextWithCountSpaceBetween( text = "게시물", - count = userDetail.postCount, + count = userDetail.postCount ?: 0, textStyle = WalkieTypography.Body1_Normal, countTextStyle = WalkieTypography.SubTitle, ) diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/UserPageScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/UserPageScreen.kt index e4cd3789..00e95269 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/UserPageScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/UserPageScreen.kt @@ -32,6 +32,9 @@ fun UserPageScreen( nickname, state, onDateClicked = viewModel::selectDate, + onPostPreviewClicked = { uid: Long, postId: Long -> + navController.navigate(Screen.UserPostsScreen.route(uid, postId)) + }, onFollowButtonClicked = { viewModel.follow(uid) }, diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/PostPage.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/PostPage.kt index 25a47320..ff3e50e4 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/PostPage.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/tabs/PostPage.kt @@ -48,7 +48,7 @@ import java.util.* fun PostPage( isMyPage: Boolean = true, postPreviews: List, - onPostPreviewClicked: (id: Long) -> Unit, + onPostPreviewClicked: (userId: Long, postId: Long) -> Unit, onPostCreateClicked: () -> Unit, ) { Box( @@ -118,7 +118,7 @@ fun PostPage( fun PostImagePreview( modifier: Modifier = Modifier, postPreview: PostPreview, - onPostPreviewClicked: (id: Long) -> Unit = {}, + onPostPreviewClicked: (uid: Long, postId: Long) -> Unit = { _, _ -> }, ) { var dynamicFontSize by remember { mutableStateOf(0.sp) } var dynamicPaddingSize by remember { mutableStateOf(0.dp) } @@ -139,7 +139,7 @@ fun PostImagePreview( .aspectRatio(1f) .clip(RoundedCornerShape(4.dp)) .clickable { - onPostPreviewClicked(postPreview.id) + onPostPreviewClicked(postPreview.author.uid, postPreview.id) }, contentScale = ContentScale.Crop, ) diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt index aacba38d..88251b9d 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt @@ -126,7 +126,6 @@ fun RunningScreen( viewModel::takeSnapShot, viewModel::sendLike, onClickProfile = { user -> - Log.d("t", "ju0828 - c") navController.navigate("userPage/${user.uid}/${user.nickname}/${true}") }, ) diff --git a/presentation/src/main/java/com/whyranoid/presentation/theme/Color.kt b/presentation/src/main/java/com/whyranoid/presentation/theme/Color.kt index af48cc95..ceb15821 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/theme/Color.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/theme/Color.kt @@ -8,6 +8,7 @@ object WalkieColor { val Primary = Color(0xFFFB8947) val Secondary = Color(0xFFE75300) val Tertiary = Color(0xFFFAC03A) + val PrimarySurface = Color(0xFFFEE9DC) val GrayBackground = Color(0xFFF8F8F8) val GrayDisable = Color(0xFFECECEC) val GrayDefault = Color(0xFFD9D9D9) diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt index 6406e29b..b7f294b3 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt @@ -17,6 +17,7 @@ sealed interface CommunityScreenSideEffect data class CommunityScreenState( val posts: UiState> = UiState.Idle, val following: UiState> = UiState.Idle, + val isEveryPost: UiState = UiState.Success(true), ) class CommunityScreenViewModel( @@ -42,11 +43,19 @@ class CommunityScreenViewModel( getPosts() } + fun switchPostType() = intent { + reduce { + state.copy(isEveryPost = UiState.Success(state.isEveryPost.getDataOrNull()!!.not())) + } + getPosts() + } + fun getPosts() = intent { reduce { state.copy(posts = UiState.Loading) } - val result = getFollowingsPostsUseCase() + val isEveryPost = state.isEveryPost.getDataOrNull() ?: true + val result = getFollowingsPostsUseCase(isEveryPost) result.onSuccess { posts -> reduce { state.copy( diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserPostsViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserPostsViewModel.kt new file mode 100644 index 00000000..a95f6c77 --- /dev/null +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserPostsViewModel.kt @@ -0,0 +1,43 @@ +package com.whyranoid.presentation.viewmodel + + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.whyranoid.domain.model.post.Post +import com.whyranoid.domain.model.user.User +import com.whyranoid.domain.usecase.GetUserDetailUseCase +import com.whyranoid.domain.usecase.GetUserPostsUseCase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class UserPostsViewModel( + private val getUserDetailUseCase: GetUserDetailUseCase, + private val getUserPostsUseCase: GetUserPostsUseCase, +) : ViewModel() { + + private val _userPostUiState: MutableStateFlow = + MutableStateFlow(UserPostUiState.Loading) + val userPostUiState get() = _userPostUiState.asStateFlow() + + fun getUiData(uid: Long) = viewModelScope.launch { + val user = getUserDetailUseCase(uid).getOrNull()?.user + val posts = getUserPostsUseCase(uid).getOrNull() + if (user != null && posts != null) { + _userPostUiState.value = UserPostUiState.Success(user, posts) + } else { + _userPostUiState.value = UserPostUiState.Failed + } + } +} + +sealed class UserPostUiState { + data class Success( + val user: User, + val posts: List + ) : UserPostUiState() + + object Loading : UserPostUiState() + + object Failed : UserPostUiState() +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserViewModel.kt index 65116fce..ca3fe916 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserViewModel.kt @@ -51,6 +51,7 @@ class UserPageViewModel( override val container = container(UserPageState()) + // todo isFollowing 포함, 데이터 가져올 떄 까지 loading 처리 fun getUserDetail(uid: Long, isFollowing: Boolean?) = intent { reduce { state.copy(userDetailState = UiState.Loading) @@ -58,7 +59,12 @@ class UserPageViewModel( getUserDetailUseCase(uid).onSuccess { userDetail -> reduce { state.copy( - userDetailState = UiState.Success(userDetail.copy(isFollowing = isFollowing)), + userDetailState = UiState.Success( + userDetail.copy( + isFollowing = isFollowing, + postCount = state.userDetailState.getDataOrNull()?.postCount + ) + ), ) } }.onFailure {