diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d4333745..f0af9361 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,7 +16,7 @@ android { minSdk = 26 targetSdk = 33 versionCode = 1 - versionName = "1.0.8" + versionName = "1.0.9" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt index 303c0e12..b04cc7d3 100644 --- a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt +++ b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt @@ -103,7 +103,7 @@ val viewModelModule = module { single { ChallengeMainViewModel(get(), get(), get()) } single { ChallengeDetailViewModel(get()) } single { ChallengeExitViewModel(get()) } - factory { UserPageViewModel(get(), get(), get(), get(), get(), get()) } + factory { UserPageViewModel(get(), get(), get(), get(), get(), get(), get(), get()) } factory { RunningViewModel(get(), get(), get(), get(), get(), get()) } factory { RunningEditViewModel() } factory { SplashViewModel(get()) } @@ -148,7 +148,7 @@ val useCaseModule = module { single { GetPostUseCase(get()) } single { GetUserPostPreviewsUseCase(get(), get()) } single { GetUserBadgesUseCase(get()) } - single { GetUserDetailUseCase(get()) } + single { GetUserDetailUseCase(get(), get()) } single { GetRunningFollowerUseCase(get(), get()) } single { RunningFinishUseCase(get(), get()) } single { RunningStartUseCase(get(), get()) } diff --git a/data/src/main/java/com/whyranoid/data/API.kt b/data/src/main/java/com/whyranoid/data/API.kt index c65b80ff..a0b436d5 100644 --- a/data/src/main/java/com/whyranoid/data/API.kt +++ b/data/src/main/java/com/whyranoid/data/API.kt @@ -39,5 +39,7 @@ object API { const val LIKE_TOTAL = "api/walk/count-total" const val LIKE_COUNT = "api/walk/count-like" + + const val MY = "/api/walkies/my" } } diff --git a/data/src/main/java/com/whyranoid/data/datasource/UserDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/datasource/UserDataSourceImpl.kt index cda88a4f..24cae83f 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/UserDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/UserDataSourceImpl.kt @@ -1,29 +1,25 @@ package com.whyranoid.data.datasource -import com.whyranoid.data.AccountDataStore +import android.util.Log +import com.whyranoid.data.datasource.community.CommunityService import com.whyranoid.domain.datasource.UserDataSource import com.whyranoid.domain.model.user.User import com.whyranoid.domain.model.user.UserDetail -import kotlinx.coroutines.flow.first -class UserDataSourceImpl(private val dataStore: AccountDataStore) : UserDataSource { - // TODO: change to api call +class UserDataSourceImpl( + private val communityService: CommunityService, +) : UserDataSource { override suspend fun getUser(uid: Long): Result { - val savedUId = dataStore.uId.first() - return if (savedUId == uid) { - kotlin.runCatching { - val name = dataStore.userName.first() - val nickName = dataStore.nickName.first() - val imageUrl = dataStore.profileUrl.first() - User( - savedUId, - requireNotNull(name), - requireNotNull(nickName), - requireNotNull(imageUrl), - ) // TODO uid 변경 사항 적용 - } - } else { - Result.success(User.DUMMY) + return kotlin.runCatching { + val response = requireNotNull(communityService.getUserNickProfile(uid).body()) + User( + uid, + requireNotNull(response.nickname), + requireNotNull(response.nickname), + requireNotNull(response.profileImg), + ) + }.onFailure { + Log.d("UserDataSourceImpl", it.message.toString()) } } diff --git a/data/src/main/java/com/whyranoid/data/datasource/community/CommunityService.kt b/data/src/main/java/com/whyranoid/data/datasource/community/CommunityService.kt index de950e61..a9b9de2f 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/community/CommunityService.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/community/CommunityService.kt @@ -1,15 +1,22 @@ package com.whyranoid.data.datasource.community import com.whyranoid.data.API +import com.whyranoid.data.model.account.NickProfileResponse import com.whyranoid.data.model.community.request.PostLikeRequest import com.whyranoid.data.model.community.response.PostLikeResponse import retrofit2.Response import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Query interface CommunityService { @POST(API.SEND_LIKE) suspend fun likePost(@Body postLikeRequest: PostLikeRequest): Response -} \ No newline at end of file + @GET(API.WalkingControl.MY) + suspend fun getUserNickProfile( + @Query("walkieId") id: Long, + ): Response +} diff --git a/data/src/main/java/com/whyranoid/data/datasource/running/RunningService.kt b/data/src/main/java/com/whyranoid/data/datasource/running/RunningService.kt index 806d01e6..ecec6519 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/running/RunningService.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/running/RunningService.kt @@ -1,6 +1,7 @@ package com.whyranoid.data.datasource.running import com.whyranoid.data.API +import com.whyranoid.data.model.account.NickProfileResponse import com.whyranoid.data.model.running.LikeTotalResponse import com.whyranoid.data.model.running.RunningFinishRequest import com.whyranoid.data.model.running.RunningStartRequest diff --git a/data/src/main/java/com/whyranoid/data/model/account/NickProfileResponse.kt b/data/src/main/java/com/whyranoid/data/model/account/NickProfileResponse.kt new file mode 100644 index 00000000..ec6d62d8 --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/model/account/NickProfileResponse.kt @@ -0,0 +1,8 @@ +package com.whyranoid.data.model.account + +import com.google.gson.annotations.SerializedName + +data class NickProfileResponse( + @SerializedName("profileImg") val profileImg: String?, + @SerializedName("nickname") val nickname: String?, +) 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 f92f3b13..a8fc8ee7 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 @@ -7,7 +7,7 @@ data class UserDetail( val postCount: Int, val followerCount: Int, val followingCount: Int, - val isFollowing: Boolean, + val isFollowing: Boolean? = null, ) { companion object { val DUMMY = UserDetail( diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/GetUserDetailUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/GetUserDetailUseCase.kt index c60df770..5c5663ba 100644 --- a/domain/src/main/java/com/whyranoid/domain/usecase/GetUserDetailUseCase.kt +++ b/domain/src/main/java/com/whyranoid/domain/usecase/GetUserDetailUseCase.kt @@ -1,15 +1,19 @@ package com.whyranoid.domain.usecase import com.whyranoid.domain.model.user.UserDetail +import com.whyranoid.domain.repository.FollowRepository import com.whyranoid.domain.repository.UserRepository class GetUserDetailUseCase( private val userRepository: UserRepository, + private val followRepository: FollowRepository, ) { suspend operator fun invoke(uid: Long): Result { return kotlin.runCatching { val user = userRepository.getUser(uid).getOrThrow() - UserDetail(user, 0, 0, 0, true) + val followingCount = followRepository.getFollowings(user.uid).getOrThrow().size + val followerCount = followRepository.getFollowers(user.uid).getOrThrow().size + UserDetail(user, 0, followerCount, followingCount, true) } } } diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/community/FollowUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/community/FollowUseCase.kt index 630a249e..90c82003 100644 --- a/domain/src/main/java/com/whyranoid/domain/usecase/community/FollowUseCase.kt +++ b/domain/src/main/java/com/whyranoid/domain/usecase/community/FollowUseCase.kt @@ -1,6 +1,5 @@ package com.whyranoid.domain.usecase.community -import com.whyranoid.domain.model.user.User import com.whyranoid.domain.repository.AccountRepository import com.whyranoid.domain.repository.FollowRepository import kotlinx.coroutines.flow.first @@ -9,10 +8,10 @@ class FollowUseCase( private val accountRepository: AccountRepository, private val followRepository: FollowRepository, ) { - suspend operator fun invoke(other: User) { - runCatching { + suspend operator fun invoke(otherUId: Long): Result { + return runCatching { val uid = requireNotNull(accountRepository.uId.first()) - followRepository.follow(uid, other.uid) + followRepository.follow(uid, otherUId).getOrThrow() } } } diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/community/UnFollowUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/community/UnFollowUseCase.kt index 8457e643..f9da79b7 100644 --- a/domain/src/main/java/com/whyranoid/domain/usecase/community/UnFollowUseCase.kt +++ b/domain/src/main/java/com/whyranoid/domain/usecase/community/UnFollowUseCase.kt @@ -1,6 +1,5 @@ package com.whyranoid.domain.usecase.community -import com.whyranoid.domain.model.user.User import com.whyranoid.domain.repository.AccountRepository import com.whyranoid.domain.repository.FollowRepository import kotlinx.coroutines.flow.first @@ -9,10 +8,10 @@ class UnFollowUseCase( private val accountRepository: AccountRepository, private val followRepository: FollowRepository, ) { - suspend operator fun invoke(other: User) { - runCatching { + suspend operator fun invoke(otherUId: Long): Result { + return runCatching { val uid = requireNotNull(accountRepository.uId.first()) - followRepository.unfollow(uid, other.uid) + followRepository.unfollow(uid, otherUId).getOrThrow() } } } 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 ff8c5203..1ea2c586 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 @@ -12,36 +12,39 @@ 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.user.User @Composable fun PostItem( post: Post, onLikeClicked: (Long) -> Unit = {}, + onProfileClicked: (User) -> Unit = {}, ) { Column( - modifier = Modifier - .fillMaxHeight() + Modifier.fillMaxHeight(), ) { - Divider( modifier = Modifier .fillMaxWidth() - .height(1.dp) + .height(1.dp), ) - PostProfileItem(post.author) + PostProfileItem( + post.author, + onProfileClicked, + ) AsyncImage( - model = post.imageUrl, contentDescription = "게시글 사진", + model = post.imageUrl, + contentDescription = "게시글 사진", modifier = Modifier .fillMaxWidth() .aspectRatio(1f), - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, ) PostContentItem(post) { onLikeClicked(it) } - } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/component/community/PostProfileItem.kt b/presentation/src/main/java/com/whyranoid/presentation/component/community/PostProfileItem.kt index 9774ba5c..56f23a48 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/component/community/PostProfileItem.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/component/community/PostProfileItem.kt @@ -1,6 +1,7 @@ package com.whyranoid.presentation.component.community import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -22,40 +23,43 @@ import com.whyranoid.presentation.theme.WalkieColor import com.whyranoid.presentation.theme.WalkieTypography @Composable -fun PostProfileItem(user: User) { +fun PostProfileItem( + user: User, + onProfileClicked: (User) -> Unit = {}, +) { Row( - modifier = Modifier + Modifier.clickable { onProfileClicked(user) } .fillMaxWidth() .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { - Box( modifier = Modifier .size(27.dp) .border(1.dp, WalkieColor.Primary, CircleShape) .clip(CircleShape), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { AsyncImage( - model = user.imageUrl, contentDescription = "내 프로필 이미지", + model = user.imageUrl, + contentDescription = "내 프로필 이미지", modifier = Modifier .size(24.dp) .border(0.5.dp, WalkieColor.GrayBorder, CircleShape) .clip(CircleShape), - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, ) } Spacer(modifier = Modifier.size(11.dp)) Column { Text( user.nickname, - style = WalkieTypography.Body1 + style = WalkieTypography.Body1, ) Text( "Seoul, park", - style = WalkieTypography.Body2 + style = WalkieTypography.Body2, ) } } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/reusable/CheckableCustomTextField.kt b/presentation/src/main/java/com/whyranoid/presentation/reusable/CheckableCustomTextField.kt index cae9dfd6..c1899c74 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/reusable/CheckableCustomTextField.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/reusable/CheckableCustomTextField.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp +import com.whyranoid.presentation.theme.WalkieColor @Composable fun CheckableCustomTextField( @@ -37,7 +38,7 @@ fun CheckableCustomTextField( onTextChanged?.invoke(it) }, singleLine = true, - cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + cursorBrush = SolidColor(WalkieColor.Primary), textStyle = textStyle, decorationBox = { innerTextField -> Row( 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 27fd4c31..155257b6 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt @@ -182,7 +182,7 @@ fun AppScreenContent( val arguments = requireNotNull(backStackEntry.arguments) val uid = arguments.getLong(Screen.UID_ARGUMENT) val nickname = requireNotNull(arguments.getString(Screen.NICKNAME_ARGUMENT)) - val isFollowing = arguments.getBoolean(Screen.NICKNAME_ARGUMENT) + val isFollowing = arguments.getBoolean(Screen.IS_FOLLOWING_ARGUMENT) UserPageScreen(navController, uid, nickname, isFollowing) } } 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 c74d412d..90b23bef 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt @@ -95,9 +95,18 @@ fun CommunityScreen( state.posts.getDataOrNull()?.forEach { post -> item { - PostItem(post = post) { postId -> - viewModel.likePost(postId) - } + PostItem( + post = post, + onLikeClicked = { postId -> + viewModel.likePost(postId) + }, + onProfileClicked = { user -> + state.following.getDataOrNull()?.let { followings -> + val isFollowing = followings.contains(user) + navController.navigate("userPage/${user.uid}/${user.nickname}/$isFollowing") + } + }, + ) } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchFriendScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchFriendScreen.kt index 9e70d16a..c4727da9 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchFriendScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchFriendScreen.kt @@ -116,7 +116,7 @@ fun SearchFriendScreen( }, cursorBrush = SolidColor(WalkieColor.Primary), singleLine = true, - ) { + ) { innerTextField -> if (query.value.isEmpty()) { Text( text = "워키 아이디로 친구찾기", @@ -130,6 +130,7 @@ fun SearchFriendScreen( style = WalkieTypography.Body1_Normal, ) } + innerTextField() } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchedFriendItem.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchedFriendItem.kt index 0de33b71..42f3aa66 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchedFriendItem.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/community/SearchedFriendItem.kt @@ -9,10 +9,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.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 @@ -21,7 +17,6 @@ import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import com.whyranoid.domain.model.user.User import com.whyranoid.domain.model.user.UserWithFollowingState -import com.whyranoid.presentation.theme.WalkieColor import com.whyranoid.presentation.theme.WalkieTypography @Composable @@ -32,7 +27,7 @@ fun SearchedFriendItem( onClickUnFollow: (User) -> Unit = {}, onClickItem: (User) -> Unit = {}, ) { - var isFollowing by remember { mutableStateOf(userWithFollowingState.isFollowing) } + // var isFollowing by remember { mutableStateOf(userWithFollowingState.isFollowing) } Row( modifier = modifier.clickable { onClickItem(userWithFollowingState.user) }, @@ -60,25 +55,25 @@ fun SearchedFriendItem( Spacer(modifier = Modifier.width(5.dp)) - Text( - modifier = Modifier.clickable { - if (isFollowing) { - onClickUnFollow(userWithFollowingState.user) - } else { - onClickFollow( - userWithFollowingState.user, - ) - } - isFollowing = isFollowing.not() - }, - text = if (isFollowing) { - "언팔로우" - } else { - "팔로우" - }, - style = WalkieTypography.Body2.copy( - color = WalkieColor.Primary, - ), - ) +// Text( +// modifier = Modifier.clickable { +// if (isFollowing) { +// onClickUnFollow(userWithFollowingState.user) +// } else { +// onClickFollow( +// userWithFollowingState.user, +// ) +// } +// isFollowing = isFollowing.not() +// }, +// text = if (isFollowing) { +// "언팔로우" +// } else { +// "팔로우" +// }, +// style = WalkieTypography.Body2.copy( +// color = WalkieColor.Primary, +// ), +// ) } } 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 1f7c6071..90e9e3ac 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 @@ -2,7 +2,11 @@ package com.whyranoid.presentation.screens.mypage import android.annotation.SuppressLint import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +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.PaddingValues import androidx.compose.foundation.layout.Row @@ -13,6 +17,7 @@ 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.wrapContentHeight import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -51,6 +56,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import coil.compose.AsyncImage +import com.whyranoid.domain.util.EMPTY import com.whyranoid.presentation.component.bar.WalkieTopBar import com.whyranoid.presentation.reusable.TextWithCountSpaceBetween import com.whyranoid.presentation.screens.Screen @@ -77,7 +83,7 @@ fun MyPageScreen( LaunchedEffect(Unit) { val myUid = requireNotNull(viewModel.accountRepository.uId.first()) - viewModel.getUserDetail(myUid, true) + viewModel.getUserDetail(myUid, null) viewModel.getUserBadges(myUid) viewModel.getUserPostPreviews(myUid) } @@ -112,6 +118,8 @@ fun UserPageContent( onSettingsClicked: () -> Unit = {}, onLogoutClicked: () -> Unit = {}, onDateClicked: (LocalDate) -> Unit = {}, + onFollowButtonClicked: () -> Unit = {}, + onUnFollowButtonClicked: () -> Unit = {}, ) { Scaffold( topBar = { @@ -149,30 +157,73 @@ fun UserPageContent( ) Spacer(modifier = Modifier.width(20.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically, + Column( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, ) { - // TODO: 스트링 리소스 분리 - TextWithCountSpaceBetween( - text = "게시물", - count = userDetail.postCount, - textStyle = WalkieTypography.Body1_Normal, - countTextStyle = WalkieTypography.SubTitle, - ) - TextWithCountSpaceBetween( - text = "팔로워", - count = userDetail.followerCount, - textStyle = WalkieTypography.Body1_Normal, - countTextStyle = WalkieTypography.SubTitle, - ) - TextWithCountSpaceBetween( - text = "팔로잉", - count = userDetail.followingCount, - textStyle = WalkieTypography.Body1_Normal, - countTextStyle = WalkieTypography.SubTitle, - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + TextWithCountSpaceBetween( + text = "게시물", + count = userDetail.postCount, + textStyle = WalkieTypography.Body1_Normal, + countTextStyle = WalkieTypography.SubTitle, + ) + TextWithCountSpaceBetween( + text = "팔로워", + count = userDetail.followerCount, + textStyle = WalkieTypography.Body1_Normal, + countTextStyle = WalkieTypography.SubTitle, + ) + TextWithCountSpaceBetween( + text = "팔로잉", + count = userDetail.followingCount, + textStyle = WalkieTypography.Body1_Normal, + countTextStyle = WalkieTypography.SubTitle, + ) + } + nickname?.let { + val isFollowing = state.userDetailState.getDataOrNull()?.isFollowing + val followingButtonBackground = + if (isFollowing == false) WalkieColor.Primary else Color.White + val followButtonText = + if (isFollowing == true) "팔로잉" else if (isFollowing == false) "팔로우" else String.EMPTY + Box( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(horizontal = 28.dp) + .padding(vertical = 12.dp) + .clip( + RoundedCornerShape(10.dp), + ) + .border( + 1.dp, + WalkieColor.GrayBorder, + RoundedCornerShape(10.dp), + ) + .background(followingButtonBackground).clickable { + if (isFollowing == true) { + onUnFollowButtonClicked() + } else if (isFollowing == false) { + onFollowButtonClicked() + } + }, + contentAlignment = Alignment.Center, + ) { + Text( + modifier = Modifier.padding(vertical = 6.dp), + text = followButtonText, + style = WalkieTypography.Body1_SemiBold, + ) + } + } } } Spacer(Modifier.height(12.dp)) @@ -269,6 +320,7 @@ fun UserPageContent( } } } + 2 -> ChallengePage() } } 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 2e67c6c4..17901708 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 @@ -29,5 +29,11 @@ fun UserPageScreen( nickname, state, onDateClicked = viewModel::selectDate, + onFollowButtonClicked = { + viewModel.follow(uid) + }, + onUnFollowButtonClicked = { + viewModel.unFollow(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 42f72d93..42295f0c 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 @@ -1,11 +1,15 @@ package com.whyranoid.presentation.screens.mypage.tabs +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.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.padding import androidx.compose.foundation.layout.size @@ -26,6 +30,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -33,8 +38,10 @@ import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import com.whyranoid.domain.model.post.PostPreview import com.whyranoid.domain.model.post.TextVisibleState +import com.whyranoid.presentation.R import com.whyranoid.presentation.reusable.NonLazyGrid import com.whyranoid.presentation.theme.WalkieColor +import com.whyranoid.presentation.theme.WalkieTypography import java.text.SimpleDateFormat import java.util.* @@ -45,39 +52,64 @@ fun PostPage( onPostPreviewClicked: (id: Long) -> Unit, onPostCreateClicked: () -> Unit, ) { - NonLazyGrid( - columns = 3, - itemCount = postPreviews.size + 1, - contentPadding = 4, - ) { index -> - if (index < postPreviews.size) { - PostImagePreview( - postPreview = postPreviews[index], - onPostPreviewClicked = onPostPreviewClicked, - ) - } else if (isMyPage) { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f) - .clip(RoundedCornerShape(4.dp)) - .border( - width = 2.dp, - color = WalkieColor.GrayDefault, - shape = RectangleShape, - ) - .clickable { - onPostCreateClicked() - }, + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + if (isMyPage.not() && postPreviews.isEmpty()) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - Icon( - modifier = Modifier - .align(Alignment.Center) - .size(22.dp), - imageVector = Icons.Default.Add, - contentDescription = "포스트 생성", - tint = WalkieColor.GrayDefault, + Image( + modifier = Modifier.padding(top = 100.dp).size(64.dp), + painter = painterResource(id = R.drawable.ic_circle_footprint), + contentDescription = "empty post", ) + + Text( + modifier = Modifier.padding(top = 24.dp), + text = "아직 게시물이 없습니다.", + style = WalkieTypography.Body1_Normal.copy(color = WalkieColor.GrayBorder), + ) + } + } else { + NonLazyGrid( + columns = 3, + itemCount = postPreviews.size + 1, + contentPadding = 4, + ) { index -> + if (index < postPreviews.size) { + PostImagePreview( + postPreview = postPreviews[index], + onPostPreviewClicked = onPostPreviewClicked, + ) + } else if (isMyPage) { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip(RoundedCornerShape(4.dp)) + .border( + width = 2.dp, + color = WalkieColor.GrayDefault, + shape = RectangleShape, + ) + .clickable { + onPostCreateClicked() + }, + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(22.dp), + imageVector = Icons.Default.Add, + contentDescription = "포스트 생성", + tint = WalkieColor.GrayDefault, + ) + } + } } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SearchFriendViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SearchFriendViewModel.kt index 2127c8f9..a9043a01 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SearchFriendViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/SearchFriendViewModel.kt @@ -34,13 +34,13 @@ class SearchFriendViewModel( fun follow(other: User) { viewModelScope.launch { - followUseCase(other) + followUseCase(other.uid) } } fun unFollow(other: User) { viewModelScope.launch { - unFollowUseCase(other) + unFollowUseCase(other.uid) } } } 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 826675b2..0111700a 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/UserViewModel.kt @@ -1,5 +1,6 @@ package com.whyranoid.presentation.viewmodel +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.whyranoid.domain.model.challenge.Badge @@ -12,6 +13,9 @@ import com.whyranoid.domain.usecase.GetUserBadgesUseCase import com.whyranoid.domain.usecase.GetUserDetailUseCase import com.whyranoid.domain.usecase.GetUserPostPreviewsUseCase import com.whyranoid.domain.usecase.SignOutUseCase +import com.whyranoid.domain.usecase.community.FollowUseCase +import com.whyranoid.domain.usecase.community.UnFollowUseCase +import com.whyranoid.domain.util.EMPTY import com.whyranoid.presentation.model.UiState import kotlinx.coroutines.launch import org.orbitmvi.orbit.ContainerHost @@ -37,11 +41,13 @@ class UserPageViewModel( private val getUserPostPreviewsUseCase: GetUserPostPreviewsUseCase, private val getPostUseCase: GetPostUseCase, private val signOutUseCase: SignOutUseCase, + private val followUseCase: FollowUseCase, + private val unFollowUseCase: UnFollowUseCase, ) : ViewModel(), ContainerHost { override val container = container(UserPageState()) - fun getUserDetail(uid: Long, isFollowing: Boolean) = intent { + fun getUserDetail(uid: Long, isFollowing: Boolean?) = intent { reduce { state.copy(userDetailState = UiState.Loading) } @@ -52,6 +58,7 @@ class UserPageViewModel( ) } }.onFailure { + Log.d("userDetail", it.message.toString()) reduce { state.copy(userDetailState = UiState.Error(it.message.toString())) } @@ -85,12 +92,12 @@ class UserPageViewModel( userPostPreviewsState = UiState.Success(userPostPreviews), userDetailState = UiState.Success( UserDetail( - state.userDetailState.getDataOrNull()?.user ?: User.DUMMY, - state.userDetailState.getDataOrNull()?.postCount - ?: userPostPreviews.size, + state.userDetailState.getDataOrNull()?.user + ?: User.DUMMY.copy(imageUrl = String.EMPTY), // TODO 사람 실루엣 url + userPostPreviews.size, state.userDetailState.getDataOrNull()?.followerCount ?: 0, state.userDetailState.getDataOrNull()?.followingCount ?: 0, - state.userDetailState.getDataOrNull()?.isFollowing ?: false, + state.userDetailState.getDataOrNull()?.isFollowing, ), ), ) @@ -116,6 +123,46 @@ class UserPageViewModel( } } + fun follow(uid: Long) = intent { + viewModelScope.launch { + followUseCase(uid).onSuccess { + reduce { + state.copy( + userDetailState = UiState.Success( + requireNotNull(state.userDetailState.getDataOrNull()).copy( + isFollowing = true, + followerCount = ( + state.userDetailState.getDataOrNull()?.followerCount + ?: 0 + ) + 1, + ), + ), + ) + } + } + } + } + + fun unFollow(uid: Long) = intent { + viewModelScope.launch { + unFollowUseCase(uid).onSuccess { + reduce { + state.copy( + userDetailState = UiState.Success( + requireNotNull(state.userDetailState.getDataOrNull()).copy( + isFollowing = false, + followerCount = ( + state.userDetailState.getDataOrNull()?.followerCount + ?: 0 + ) - 1, + ), + ), + ) + } + } + } + } + fun signOut() { viewModelScope.launch { signOutUseCase() diff --git a/presentation/src/main/res/drawable/ic_circle_footprint.png b/presentation/src/main/res/drawable/ic_circle_footprint.png new file mode 100644 index 00000000..5aec7518 Binary files /dev/null and b/presentation/src/main/res/drawable/ic_circle_footprint.png differ