From 85377bbe657410a3a11270d960bb43d4665d15b5 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 01:59:06 +0900 Subject: [PATCH 01/35] =?UTF-8?q?[feat]:=20getOthersFollowers=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../texthip/thip/data/repository/UserRepository.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index e048f8cb..4c73682e 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -2,6 +2,7 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse import com.texthip.thip.data.model.users.MyFollowingsResponse +import com.texthip.thip.data.model.users.OthersFollowersResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject import javax.inject.Singleton @@ -18,4 +19,14 @@ class UserRepository@Inject constructor( .handleBaseResponse() .getOrThrow() } + + suspend fun getOthersFollowers( + userId: Long, + cursor: String?, + size: Int = 10 + ): Result = runCatching { + userService.getUserFollowers(userId = userId, cursor = cursor, size = size) + .handleBaseResponse() + .getOrThrow() + } } \ No newline at end of file From a4f10c5a71177dcc05af99025776d540f9de8326 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 01:59:13 +0900 Subject: [PATCH 02/35] =?UTF-8?q?[feat]:=20getUserFollowers=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/texthip/thip/data/service/UserService.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt index b2c1f429..70817c81 100644 --- a/app/src/main/java/com/texthip/thip/data/service/UserService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -1,8 +1,8 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse -import com.texthip.thip.data.model.rooms.response.RoomsUsersResponse import com.texthip.thip.data.model.users.MyFollowingsResponse +import com.texthip.thip.data.model.users.OthersFollowersResponse import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query @@ -14,4 +14,10 @@ interface UserService { @Query("cursor") cursor: String? = null ): BaseResponse + @GET("users/{userId}/followers") + suspend fun getUserFollowers( + @Path("userId") userId: Long, + @Query("size") size: Int = 10, + @Query("cursor") cursor: String? = null + ): BaseResponse } \ No newline at end of file From fb30a12653b3e5abde1087509fa82693d19e4605 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 01:59:37 +0900 Subject: [PATCH 03/35] =?UTF-8?q?[feat]:=20=EB=8B=A4=EB=A5=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=ED=8C=94=EB=A1=9C=EC=9B=8C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../viewmodel/OthersSubscriptionViewModel.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt new file mode 100644 index 00000000..ef38b5d0 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt @@ -0,0 +1,68 @@ +package com.texthip.thip.ui.feed.viewmodel + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.model.users.FollowerList +import com.texthip.thip.data.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class OthersSubscriptionUiState( + val isLoading: Boolean = false, + val followers: List = emptyList(), + val totalCount: Int = 0, + val isLastPage: Boolean = false, + val errorMessage: String? = null +) + +@HiltViewModel +class OthersSubscriptionViewModel @Inject constructor( + private val userRepository: UserRepository, + savedStateHandle: SavedStateHandle +) : ViewModel() { + private val userId: Long = requireNotNull(savedStateHandle["userId"]) + private val _uiState = MutableStateFlow(OthersSubscriptionUiState()) + val uiState = _uiState.asStateFlow() + + private var nextCursor: String? = null + + init { + fetchOthersFollowers(isInitial = true) + } + + fun fetchOthersFollowers(isInitial: Boolean = false) { + if (_uiState.value.isLoading || (!isInitial && _uiState.value.isLastPage)) { + return + } + + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + val cursorToFetch = if (isInitial) null else nextCursor + + val result = userRepository.getOthersFollowers(userId = userId, cursor = cursorToFetch) + + result.onSuccess { data -> + data?.let { + _uiState.update { currentState -> + currentState.copy( + isLoading = false, + followers = if (isInitial) it.followers else currentState.followers + currentState.followers, + totalCount = it.totalFollowerCount, + isLastPage = it.isLast + ) + } + nextCursor = it.nextCursor + } + }.onFailure { exception -> + _uiState.update { + it.copy(isLoading = false, errorMessage = exception.message) + } + } + } + } +} \ No newline at end of file From 3f1bfa53a9d82b495c2b2eb7f7ea1d58d3b49a71 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 01:59:53 +0900 Subject: [PATCH 04/35] =?UTF-8?q?[feat]:=20=EB=8B=A4=EB=A5=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=ED=8C=94=EB=A1=9C=EC=9B=8C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=ED=99=94=EB=A9=B4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20content=20screen=20=EB=B6=84=EB=A6=AC(#?= =?UTF-8?q?73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/OthersSubscriptionListScreen.kt | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt new file mode 100644 index 00000000..de305dba --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt @@ -0,0 +1,181 @@ +package com.texthip.thip.ui.feed.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.texthip.thip.R +import com.texthip.thip.data.model.users.FollowerList +import com.texthip.thip.ui.common.header.AuthorHeader +import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar +import com.texthip.thip.ui.feed.viewmodel.OthersSubscriptionUiState +import com.texthip.thip.ui.feed.viewmodel.OthersSubscriptionViewModel +import com.texthip.thip.ui.theme.ThipTheme +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography +import com.texthip.thip.utils.color.hexToColor + +@Composable +fun OthersSubsciptionListScreen( + navController: NavController, + viewModel: OthersSubscriptionViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + val lazyListState = rememberLazyListState() + + val isScrolledToEnd by remember { + derivedStateOf { + val layoutInfo = lazyListState.layoutInfo + if (layoutInfo.totalItemsCount == 0) return@derivedStateOf false + val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 + lastVisibleItemIndex >= layoutInfo.totalItemsCount - 1 + } + } + + LaunchedEffect(isScrolledToEnd) { + if (isScrolledToEnd && !uiState.isLoading && !uiState.isLastPage) { + viewModel.fetchOthersFollowers() + } + } + + OthersSubsciptionContent( + uiState = uiState, + lazyListState = lazyListState, + onNavigateBack = { navController.popBackStack() } + ) +} +@Composable +fun OthersSubsciptionContent( + uiState: OthersSubscriptionUiState, + lazyListState: LazyListState, + onNavigateBack: () -> Unit +) { + Column( + Modifier + .background(colors.Black) + .fillMaxSize() + ) { + DefaultTopAppBar( + onLeftClick = onNavigateBack, + title = stringResource(R.string.thip_list) + ) + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(40.dp)) + Text( + text = stringResource(R.string.whole_num, uiState.totalCount), + style = typography.menu_m500_s14_h24, + color = colors.Grey, + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, bottom = 4.dp) + ) + HorizontalDivider( + modifier = Modifier.padding(horizontal = 20.dp), + color = colors.DarkGrey02, + thickness = 1.dp + ) + + LazyColumn( + state = lazyListState, + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(vertical = 20.dp) + ) { + itemsIndexed( + items = uiState.followers, + key = { _, user -> user.userId } + ) { index, user -> + Column(modifier = Modifier.padding(horizontal = 20.dp)) { + AuthorHeader( + profileImage = user.profileImageUrl, + nickname = user.nickname, + badgeText = user.aliasName, + badgeTextColor = hexToColor(user.aliasColor), + showButton = false, + showThipNum = true, + profileImageSize = 36.dp, + thipNum = user.followerCount, + onThipNumClick = {} + ) + + if (index < uiState.followers.lastIndex) { + Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider( + color = colors.DarkGrey02, + thickness = 1.dp + ) + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + + if (uiState.isLoading && !uiState.isLastPage) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.weight(1f)) + CircularProgressIndicator(modifier = Modifier.size(24.dp)) + Spacer(modifier = Modifier.weight(1f)) + } + } + } + } + } + } +} +@Preview +@Composable +private fun OthersSubsciptionListScreenPrev() { + val mockUsers = (1..10).map { + FollowerList( + userId = it.toLong(), + profileImageUrl = null, + nickname = "문학소년 $it", + aliasName = if (it % 3 == 0) "공식 인플루언서" else "글쓰는 탐험가", + aliasColor = if (it % 3 == 0) "#00C7B2" else "#FFD600", + followerCount = it * 10 + ) + } + + ThipTheme { + OthersSubsciptionContent( + uiState = OthersSubscriptionUiState( + isLoading = false, + followers = mockUsers, + totalCount = mockUsers.size, + isLastPage = false + ), + lazyListState = rememberLazyListState(), + onNavigateBack = {} + ) + } +} \ No newline at end of file From eccfb14c7c99c7fa27c8e95c270c796c49ae12b5 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 02:16:27 +0900 Subject: [PATCH 05/35] =?UTF-8?q?[feat]:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A0=95=EB=B3=B4=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?dto(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/model/users/MyPageInfoResponse.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt new file mode 100644 index 00000000..76d86893 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt @@ -0,0 +1,12 @@ +package com.texthip.thip.data.model.users + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MyPageInfoResponse( + @SerialName("profileImageUrl") val profileImageUrl: String?, + @SerialName("nickname") val nickname: String, + @SerialName("aliasName") val aliasName: String, + @SerialName("aliasColor") val aliasColor: String +) From 09795c5bc6e6b4b662ad8960d501a4f0a614a498 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 02:16:56 +0900 Subject: [PATCH 06/35] =?UTF-8?q?[feat]:=20getMyPage=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=83=9D=EC=84=B1(#7?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/texthip/thip/data/service/UserService.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt index 70817c81..0c50e37c 100644 --- a/app/src/main/java/com/texthip/thip/data/service/UserService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -2,6 +2,7 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse import com.texthip.thip.data.model.users.MyFollowingsResponse +import com.texthip.thip.data.model.users.MyPageInfoResponse import com.texthip.thip.data.model.users.OthersFollowersResponse import retrofit2.http.GET import retrofit2.http.Path @@ -20,4 +21,7 @@ interface UserService { @Query("size") size: Int = 10, @Query("cursor") cursor: String? = null ): BaseResponse + + @GET("users/my-page") + suspend fun getMyPage(): BaseResponse } \ No newline at end of file From 8ad2f23db66f20e62548fb7a4dfadf94d336f2c7 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 02:17:27 +0900 Subject: [PATCH 07/35] =?UTF-8?q?[feat]:=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=97=90=20getMyPageInfo=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/data/repository/UserRepository.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index 4c73682e..10099e28 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -2,6 +2,7 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse import com.texthip.thip.data.model.users.MyFollowingsResponse +import com.texthip.thip.data.model.users.MyPageInfoResponse import com.texthip.thip.data.model.users.OthersFollowersResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject @@ -11,6 +12,7 @@ import javax.inject.Singleton class UserRepository@Inject constructor( private val userService: UserService ) { + //내 팔로잉 목록 조회 suspend fun getMyFollowings( cursor: String?, size: Int = 10 @@ -20,6 +22,7 @@ class UserRepository@Inject constructor( .getOrThrow() } + //다른 유저 팔로워 목록 조회 suspend fun getOthersFollowers( userId: Long, cursor: String?, @@ -29,4 +32,11 @@ class UserRepository@Inject constructor( .handleBaseResponse() .getOrThrow() } + + //마이페이지 정보 조회 + suspend fun getMyPageInfo(): Result = runCatching { + userService.getMyPage() + .handleBaseResponse() + .getOrThrow() + } } \ No newline at end of file From 53e6a6a61646cefa79f29fd373ab9b41a5b8379c Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 15:14:11 +0900 Subject: [PATCH 08/35] =?UTF-8?q?[feat]:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/mypage/viewmodel/MyPageViewModel.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt new file mode 100644 index 00000000..bf569aec --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt @@ -0,0 +1,69 @@ +package com.texthip.thip.ui.mypage.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class MyPageUiState( + val isLoading: Boolean = true, + val profileImageUrl: String? = null, + val nickname: String = "", + val aliasName: String = "", + val aliasColor: String = "#0XFFFFFF", + val errorMessage: String? = null, + val showLogoutDialog: Boolean = false +) +@HiltViewModel +class MyPageViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(MyPageUiState()) + val uiState = _uiState.asStateFlow() + + init { + fetchMyPageInfo() + } + + fun fetchMyPageInfo() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + userRepository.getMyPageInfo() + .onSuccess { data -> + data?.let { + _uiState.update { + it.copy( + isLoading = false, + profileImageUrl = data.profileImageUrl, + nickname = data.nickname, + aliasName = data.aliasName, + aliasColor = data.aliasColor + ) + } + } + } + .onFailure { exception -> + _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } + } + } + } + + fun onLogoutClick() { + _uiState.update { it.copy(showLogoutDialog = true) } + } + + fun onDismissLogoutDialog() { + _uiState.update { it.copy(showLogoutDialog = false) } + } + + fun confirmLogout() { + _uiState.update { it.copy(showLogoutDialog = false) } + // TODO: 실제 로그아웃 로직 구현 + } +} \ No newline at end of file From 57071ea2a3c417faeffd19d0854055ac3d409819 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 15:14:27 +0900 Subject: [PATCH 09/35] =?UTF-8?q?[feat]:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20screen=20content=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=A0=81=EC=9A=A9(#7?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/screen/MypageScreen.kt | 335 ++++++++++-------- 1 file changed, 186 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt index 4797ea41..b91bef0f 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt @@ -11,14 +11,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState 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.res.painterResource @@ -26,178 +22,208 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.texthip.thip.R import com.texthip.thip.ui.common.buttons.MenuItemButton import com.texthip.thip.ui.common.header.AuthorHeader import com.texthip.thip.ui.common.modal.DialogPopup import com.texthip.thip.ui.common.topappbar.LeftNameTopAppBar +import com.texthip.thip.ui.mypage.viewmodel.MyPageUiState +import com.texthip.thip.ui.mypage.viewmodel.MyPageViewModel import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import com.texthip.thip.utils.color.hexToColor @Composable fun MyPageScreen( - navController: NavController? = null, - nickname: String, - badgeText: String + navController: NavController, + viewModel: MyPageViewModel = hiltViewModel(), + onNavigateToEditProfile: () -> Unit, + onNavigateToSavedFeeds: () -> Unit, + onNavigateToNotificationSettings: () -> Unit, + onDeleteAccount: () -> Unit ) { - var showLogoutDialog by remember { mutableStateOf(false) } + val uiState by viewModel.uiState.collectAsState() - Column( + MyPageContent( + uiState = uiState, + onEditProfileClick = onNavigateToEditProfile, + onSavedFeedsClick = onNavigateToSavedFeeds, + onNotificationSettingsClick = onNavigateToNotificationSettings, + onLogoutClick = { viewModel.onLogoutClick() }, + onDismissLogoutDialog = { viewModel.onDismissLogoutDialog() }, + onConfirmLogout = { viewModel.confirmLogout() }, + onDeleteAccount = onDeleteAccount + ) +} +@Composable +fun MyPageContent( + uiState: MyPageUiState, + onEditProfileClick: () -> Unit, + onSavedFeedsClick: () -> Unit, + onNotificationSettingsClick: () -> Unit, + onLogoutClick: () -> Unit, + onDismissLogoutDialog: () -> Unit, + onConfirmLogout: () -> Unit, + onDeleteAccount: () -> Unit +) { + Box( Modifier .background(colors.Black) .fillMaxSize() ) { - LeftNameTopAppBar( - title = stringResource(R.string.my_page), - leftIcon = painterResource(R.drawable.ic_search), - rightIcon = painterResource(R.drawable.ic_plus) - ) - LazyColumn( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(0.dp) + Column( + Modifier + .background(colors.Black) + .fillMaxSize() ) { - item { - Spacer(modifier = Modifier.height(20.dp)) - AuthorHeader( - profileImage = null, - nickname = nickname, - badgeText = badgeText, - buttonText = stringResource(R.string.edit) - ) - Spacer(modifier = Modifier.height(40.dp)) - } - item { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp) - ) { - Text( - text = stringResource(R.string.my_activity), - style = typography.smalltitle_sb600_s18_h24, - color = colors.White, + LeftNameTopAppBar( + title = stringResource(R.string.my_page), + leftIcon = painterResource(R.drawable.ic_search), + rightIcon = painterResource(R.drawable.ic_plus) + ) + LazyColumn( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(0.dp) + ) { + item { + Spacer(modifier = Modifier.height(20.dp)) + AuthorHeader( + profileImage = uiState.profileImageUrl, + nickname = uiState.nickname, + badgeText = uiState.aliasName, + badgeTextColor = hexToColor(uiState.aliasColor), + buttonText = stringResource(R.string.edit), + onButtonClick = onEditProfileClick + ) + Spacer(modifier = Modifier.height(40.dp)) + } + item { + Column( modifier = Modifier .fillMaxWidth() - .padding(bottom = 12.dp) - ) - MenuItemButton( - text = stringResource(R.string.saved), - icon = painterResource(R.drawable.ic_save), - contentColor = colors.White, - backgroundColor = colors.DarkGrey02, - hasRightIcon = true, - modifier = Modifier.fillMaxWidth(), - onClick = {} - ) + .padding(horizontal = 20.dp) + ) { + Text( + text = stringResource(R.string.my_activity), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) + ) + MenuItemButton( + text = stringResource(R.string.saved), + icon = painterResource(R.drawable.ic_save), + contentColor = colors.White, + backgroundColor = colors.DarkGrey02, + hasRightIcon = true, + modifier = Modifier.fillMaxWidth(), + onClick = onSavedFeedsClick + ) + } + Spacer(modifier = Modifier.height(40.dp)) } - Spacer(modifier = Modifier.height(40.dp)) - } - item { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp) - ) { - Text( - text = stringResource(R.string.etc), - style = typography.smalltitle_sb600_s18_h24, - color = colors.White, + item { + Column( modifier = Modifier .fillMaxWidth() - .padding(bottom = 12.dp) - ) - MenuItemButton( - text = stringResource(R.string.notification_settings), - icon = painterResource(R.drawable.ic_notice), - contentColor = colors.White, - backgroundColor = colors.DarkGrey02, - hasRightIcon = true, - modifier = Modifier.fillMaxWidth(), - onClick = {} - ) - Spacer(modifier = Modifier.height(16.dp)) - MenuItemButton( - text = stringResource(R.string.customer_service), - icon = painterResource(R.drawable.ic_center), - contentColor = colors.White, - backgroundColor = colors.DarkGrey02, - hasRightIcon = true, - modifier = Modifier.fillMaxWidth(), - onClick = {} - ) - Spacer(modifier = Modifier.height(16.dp)) - MenuItemButton( - text = stringResource(R.string.terms_of_use), - icon = painterResource(R.drawable.ic_doc), - contentColor = colors.White, - backgroundColor = colors.DarkGrey02, - hasRightIcon = true, - modifier = Modifier.fillMaxWidth(), - onClick = {} - ) - Spacer(modifier = Modifier.height(16.dp)) - MenuItemButton( - text = stringResource(R.string.guide), - icon = painterResource(R.drawable.ic_guide), - contentColor = colors.White, - backgroundColor = colors.DarkGrey02, - hasRightIcon = true, - modifier = Modifier.fillMaxWidth(), - onClick = {} - ) - Spacer(modifier = Modifier.height(16.dp)) - MenuItemButton( - text = stringResource(R.string.version_1_0), - icon = painterResource(R.drawable.ic_version), - contentColor = colors.White, - backgroundColor = colors.DarkGrey02, - hasRightIcon = true, - modifier = Modifier.fillMaxWidth(), - onClick = {} - ) + .padding(horizontal = 20.dp) + ) { + Text( + text = stringResource(R.string.etc), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) + ) + MenuItemButton( + text = stringResource(R.string.notification_settings), + icon = painterResource(R.drawable.ic_notice), + contentColor = colors.White, + backgroundColor = colors.DarkGrey02, + hasRightIcon = true, + modifier = Modifier.fillMaxWidth(), + onClick = onNotificationSettingsClick + ) + Spacer(modifier = Modifier.height(16.dp)) + MenuItemButton( + text = stringResource(R.string.customer_service), + icon = painterResource(R.drawable.ic_center), + contentColor = colors.White, + backgroundColor = colors.DarkGrey02, + hasRightIcon = true, + modifier = Modifier.fillMaxWidth(), + onClick = {} + ) + Spacer(modifier = Modifier.height(16.dp)) + MenuItemButton( + text = stringResource(R.string.terms_of_use), + icon = painterResource(R.drawable.ic_doc), + contentColor = colors.White, + backgroundColor = colors.DarkGrey02, + hasRightIcon = true, + modifier = Modifier.fillMaxWidth(), + onClick = {} + ) + Spacer(modifier = Modifier.height(16.dp)) + MenuItemButton( + text = stringResource(R.string.guide), + icon = painterResource(R.drawable.ic_guide), + contentColor = colors.White, + backgroundColor = colors.DarkGrey02, + hasRightIcon = true, + modifier = Modifier.fillMaxWidth(), + onClick = {} + ) + Spacer(modifier = Modifier.height(16.dp)) + MenuItemButton( + text = stringResource(R.string.version_1_0), + icon = painterResource(R.drawable.ic_version), + contentColor = colors.White, + backgroundColor = colors.DarkGrey02, + hasRightIcon = true, + modifier = Modifier.fillMaxWidth(), + onClick = {} + ) + } + Spacer(modifier = Modifier.height(184.dp)) } - Spacer(modifier = Modifier.height(184.dp)) - } - item { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 40.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = stringResource(R.string.log_out), - style = typography.feedcopy_r400_s14_h20, - color = colors.Grey01, - modifier = Modifier.clickable { showLogoutDialog = true } - ) - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = stringResource(R.string.delete_account), - style = typography.feedcopy_r400_s14_h20, - color = colors.Grey01, - modifier = Modifier.clickable { - // TODO: 탈퇴 로직 - } - ) + item { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 40.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.log_out), + style = typography.feedcopy_r400_s14_h20, + color = colors.Grey01, + modifier = Modifier.clickable { onLogoutClick() } + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.delete_account), + style = typography.feedcopy_r400_s14_h20, + color = colors.Grey01, + modifier = Modifier.clickable { onDeleteAccount() } + ) + } } } } - - if (showLogoutDialog) { - Dialog(onDismissRequest = { showLogoutDialog = false }) { + if (uiState.showLogoutDialog) { + Dialog(onDismissRequest = { onDismissLogoutDialog() }) { DialogPopup( - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.log_out), description = stringResource(R.string.logout_description), - onCancel = { showLogoutDialog = false }, - onConfirm = { - showLogoutDialog = false - // TODO: 로그아웃 로직 - } + onCancel = { onDismissLogoutDialog() }, + onConfirm = { onConfirmLogout() } ) } } @@ -209,9 +235,20 @@ fun MyPageScreen( @Composable private fun MyPagePrev() { ThipTheme { - MyPageScreen( - nickname = "ThipUser01", - badgeText = "문학가" + MyPageContent( + uiState = MyPageUiState( + isLoading = false, + nickname = "ThipUser01", + aliasName = "문학가", + aliasColor = "#FFFFFF" + ), + onLogoutClick = {}, + onEditProfileClick = {}, + onSavedFeedsClick = {}, + onNotificationSettingsClick = {}, + onDismissLogoutDialog = {}, + onConfirmLogout = {}, + onDeleteAccount = {} ) } } \ No newline at end of file From 0a2140c03459abc5ec9ecac8f899277b1107b264 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 15:14:50 +0900 Subject: [PATCH 10/35] =?UTF-8?q?[feat]:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20navigation=20=ED=99=95=EC=9E=A5=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extensions/MyPageNavigationExtensions.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/MyPageNavigationExtensions.kt b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/MyPageNavigationExtensions.kt index 4ca4453a..153d958e 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/MyPageNavigationExtensions.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/MyPageNavigationExtensions.kt @@ -2,6 +2,7 @@ package com.texthip.thip.ui.navigator.extensions import androidx.navigation.NavHostController import com.texthip.thip.ui.navigator.routes.MainTabRoutes +import com.texthip.thip.ui.navigator.routes.MyPageRoutes // MyPage 관련 네비게이션 확장 함수들 @@ -9,3 +10,18 @@ import com.texthip.thip.ui.navigator.routes.MainTabRoutes fun NavHostController.navigateToMyPage() { navigate(MainTabRoutes.MyPage) } +fun NavHostController.navigateToEditProfile() { + navigate(MyPageRoutes.Edit) +} + +fun NavHostController.navigateToSavedFeeds() { + navigate(MyPageRoutes.Save) +} + +fun NavHostController.navigateToNotificationSettings() { + navigate(MyPageRoutes.NotificationEdit) +} + +fun NavHostController.navigateToLeaveThipScreen() { + navigate(MyPageRoutes.LeaveThip) +} \ No newline at end of file From 91d1c2f18fe61b9f0405f25880722e2aa75f37f3 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 15:15:04 +0900 Subject: [PATCH 11/35] =?UTF-8?q?[feat]:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20navigation=20=EC=A0=81=EC=9A=A9(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../navigator/navigations/MyPageNavigation.kt | 30 +++++++++++++++++-- .../thip/ui/navigator/routes/MyPageRoutes.kt | 10 +++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt index 92883f8f..f318dfb6 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt @@ -1,19 +1,43 @@ package com.texthip.thip.ui.navigator.navigations +import androidx.compose.material3.Text import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable +import com.texthip.thip.ui.mypage.screen.DeleteAccountScreen +import com.texthip.thip.ui.mypage.screen.EditProfileScreen import com.texthip.thip.ui.mypage.screen.MyPageScreen +import com.texthip.thip.ui.mypage.screen.NotificationScreen +import com.texthip.thip.ui.mypage.screen.SavedScreen +import com.texthip.thip.ui.navigator.extensions.navigateToEditProfile +import com.texthip.thip.ui.navigator.extensions.navigateToLeaveThipScreen +import com.texthip.thip.ui.navigator.extensions.navigateToNotificationSettings +import com.texthip.thip.ui.navigator.extensions.navigateToSavedFeeds import com.texthip.thip.ui.navigator.routes.MainTabRoutes +import com.texthip.thip.ui.navigator.routes.MyPageRoutes // MyPage fun NavGraphBuilder.myPageNavigation(navController: NavHostController) { composable { MyPageScreen( - navController, - nickname = "ThipUser01", - badgeText = "문학가" + navController = navController, + onNavigateToEditProfile = { navController.navigateToEditProfile() }, + onNavigateToSavedFeeds = { navController.navigateToSavedFeeds() }, + onNavigateToNotificationSettings = { navController.navigateToNotificationSettings() }, + onDeleteAccount = { navController.navigateToLeaveThipScreen() } ) } + composable { + EditProfileScreen() + } + composable { + SavedScreen() + } + composable { + NotificationScreen() + } + composable { + DeleteAccountScreen() + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/routes/MyPageRoutes.kt b/app/src/main/java/com/texthip/thip/ui/navigator/routes/MyPageRoutes.kt index 85ab317f..3c0beed9 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/routes/MyPageRoutes.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/routes/MyPageRoutes.kt @@ -4,9 +4,9 @@ import kotlinx.serialization.Serializable @Serializable sealed class MyPageRoutes : Routes() { - // 향후 추가될 MyPage 관련 화면들 - // @Serializable data object Edit : MyPageRoutes - // @Serializable data object Save : MyPageRoutes - // @Serializable data object Reaction : MyPageRoutes - // @Serializable data object NotificationEdit : MyPageRoutes + @Serializable data object Edit : MyPageRoutes() + @Serializable data object Save : MyPageRoutes() + @Serializable data object Reaction : MyPageRoutes() + @Serializable data object NotificationEdit : MyPageRoutes() + @Serializable data object LeaveThip : MyPageRoutes() } \ No newline at end of file From 2a5fdfff997a7af3afd327a3a08b27c032326c24 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 16:36:36 +0900 Subject: [PATCH 12/35] =?UTF-8?q?[ui]:=20CardBookList=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20-=20=EB=B6=81=EB=A7=88=ED=81=AC=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EB=8B=A4=EC=8B=9C=20=EB=B3=B4=EC=9D=B4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/common/cards/CardBookList.kt | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt index 717dd18f..8bb88cc5 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt @@ -10,6 +10,8 @@ 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.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -18,7 +20,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -34,6 +38,7 @@ fun CardBookList( author: String, publisher: String, imageUrl: String? = null, // API에서 받은 이미지 URL + showBookmark: Boolean = false, isBookmarked: Boolean = false, onBookmarkClick: () -> Unit = {} ) { @@ -74,17 +79,20 @@ fun CardBookList( Spacer(modifier = Modifier.width(12.dp)) - // 북마크 아이콘 제거(쓰는 화면이 안보임) - /*IconButton( - onClick = onBookmarkClick, - modifier = Modifier.size(24.dp) - ) { - Icon( - imageVector = if (isBookmarked) ImageVector.vectorResource(R.drawable.ic_save_filled) else ImageVector.vectorResource(R.drawable.ic_save), - contentDescription = "북마크", - tint = if (isBookmarked) colors.Purple else colors.Grey01 - ) - }*/ + if(showBookmark) { + IconButton( + onClick = onBookmarkClick, + modifier = Modifier.size(24.dp) + ) { + Icon( + imageVector = if (isBookmarked) ImageVector.vectorResource(R.drawable.ic_save_filled) else ImageVector.vectorResource( + R.drawable.ic_save + ), + contentDescription = "북마크", + tint = if (isBookmarked) colors.Purple else colors.Grey01 + ) + } + } } } @@ -93,6 +101,7 @@ fun CardBookList( @Composable fun PreviewBookTitleCard() { var isBookmarked by remember { mutableStateOf(false) } + var showBookmark by remember { mutableStateOf(true) } Column( modifier = Modifier.padding(16.dp), @@ -102,9 +111,9 @@ fun PreviewBookTitleCard() { title = "책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.", author = "리처드 도킨스", publisher = "을유문화사", + showBookmark = showBookmark, isBookmarked = isBookmarked, onBookmarkClick = { isBookmarked = !isBookmarked } ) } - } \ No newline at end of file From 3411239f111f65bb21303f095b1ace96378e1181 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 16:37:03 +0900 Subject: [PATCH 13/35] =?UTF-8?q?[ui]:=20bookContent=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20-=20padding=EA=B0=92=20=EB=8B=A4=EC=8B=9C=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/component/BookContent.kt | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt b/app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt index 6c5ff75d..17b63d74 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/component/BookContent.kt @@ -2,10 +2,15 @@ package com.texthip.thip.ui.mypage.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -28,16 +33,34 @@ fun BookContent( if (bookList.isEmpty()) { EmptyBookContent() } else { - LazyColumn { - items(bookList, key = { it.id }) { book -> + LazyColumn ( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + ){ + itemsIndexed(bookList, key = { _, book -> book.id }) { index, book -> + if (index == 0) { + Spacer(Modifier.height(32.dp)) + } + CardBookList( title = book.title, author = book.author, imageUrl = null, publisher = book.publisher, + showBookmark = true, isBookmarked = book.isSaved, onBookmarkClick = { viewModel.toggleBookmark(book.id) } ) + + if (index != bookList.lastIndex) { + Spacer(Modifier.height(20.dp)) + HorizontalDivider( + color = colors.DarkGrey02, + thickness = 1.dp + ) + Spacer(Modifier.height(20.dp)) + } } } } From 76eabb990fdaa77e91da9dec051e35a2cf27aff5 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 16:37:24 +0900 Subject: [PATCH 14/35] =?UTF-8?q?[ui]:=20FeedContent=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20-=20padding=EA=B0=92=20=EB=8B=A4=EC=8B=9C=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/component/FeedContent.kt | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt b/app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt index 078eba10..3670ba8b 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/component/FeedContent.kt @@ -2,21 +2,19 @@ package com.texthip.thip.ui.mypage.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +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.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.texthip.thip.R import com.texthip.thip.ui.mypage.mock.FeedItem import com.texthip.thip.ui.mypage.viewmodel.SavedFeedViewModel @@ -31,16 +29,29 @@ fun FeedContent( if (feedList.isEmpty()) { EmptyFeedContent() } else { - LazyColumn { - items(feedList, key = { it.id }) { feed -> - val profileImagePainter = feed.userProfileImage?.let { painterResource(it) } + LazyColumn ( + modifier = Modifier + .fillMaxSize() + ){ + itemsIndexed(feedList, key = { _,feed -> feed.id }) { index,feed -> + if (index == 0) { + Spacer(Modifier.height(32.dp)) + } SavedFeedCard( feedItem = feed, - profileImage = profileImagePainter, onBookmarkClick = { viewModel.toggleBookmark(feed.id) }, onLikeClick = { viewModel.toggleLike(feed.id) } ) + + if (index != feedList.lastIndex) { + Spacer(Modifier.height(40.dp)) + HorizontalDivider( + color = colors.DarkGrey03, + thickness = 6.dp + ) + Spacer(Modifier.height(40.dp)) + } } } } From bacbc40bd8cd0257b6b161a76c7549482dc09932 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 16:37:49 +0900 Subject: [PATCH 15/35] =?UTF-8?q?[refactor]:=20FeedScreen->=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20profileImage=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt index 45220924..c4ff2cfb 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt @@ -234,7 +234,6 @@ fun FeedScreen( SavedFeedCard( feedItem = feed, - profileImage = profileImage, onBookmarkClick = { val updated = feed.copy(isSaved = !feed.isSaved) feedStateList[index] = updated From 2c0394fa41a099ce9cfc30b18d02f59f7135e716 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 16:38:46 +0900 Subject: [PATCH 16/35] =?UTF-8?q?[refactor]:=20SavedFeedCard->=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20profileImage=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/component/SavedFeedCard.kt | 17 +++++++++-------- .../ui/mypage/viewmodel/SavedBookViewModel.kt | 1 - 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt b/app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt index d0d6198a..53255ff0 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/component/SavedFeedCard.kt @@ -34,7 +34,6 @@ import com.texthip.thip.ui.theme.ThipTheme.typography fun SavedFeedCard( modifier: Modifier = Modifier, feedItem: FeedItem, - profileImage: Painter? = null, onBookmarkClick: () -> Unit = {}, onLikeClick: () -> Unit = {}, onContentClick: () -> Unit = {} @@ -47,10 +46,10 @@ fun SavedFeedCard( Column( modifier = modifier .fillMaxWidth() - .padding(20.dp) + .padding(horizontal = 20.dp) ) { ProfileBar( - profileImage = "https://example.com/image1.jpg", + profileImage = feedItem.userProfileImage.toString(), topText = feedItem.userName, bottomText = feedItem.userRole, showSubscriberInfo = false, @@ -166,7 +165,11 @@ private fun SavedFeedCardPrev() { commentCount = 5, isLiked = false, isSaved = true, - imageUrls = listOf(R.drawable.img_book_cover_sample,R.drawable.img_book_cover_sample,R.drawable.img_book_cover_sample) + imageUrls = listOf( + R.drawable.img_book_cover_sample, + R.drawable.img_book_cover_sample, + R.drawable.img_book_cover_sample + ) ) val scrollState = rememberScrollState() @@ -177,12 +180,10 @@ private fun SavedFeedCardPrev() { .verticalScroll(scrollState) ) { SavedFeedCard( - feedItem = feed1, - profileImage = painterResource(feed1.userProfileImage!!) + feedItem = feed1 ) SavedFeedCard( - feedItem = feed2, - profileImage = painterResource(feed2.userProfileImage!!) + feedItem = feed2 ) } } diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt index fec20fc2..5e2fca6a 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/SavedBookViewModel.kt @@ -2,7 +2,6 @@ package com.texthip.thip.ui.mypage.viewmodel import androidx.lifecycle.ViewModel import com.texthip.thip.ui.mypage.mock.BookItem -import com.texthip.thip.ui.mypage.mock.FeedItem import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow From 86ab8e58dc441b935af96940590d5a40211e033a Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 16:39:21 +0900 Subject: [PATCH 17/35] =?UTF-8?q?[ui]:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A0=80=EC=9E=A5=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=B0=20-=20content=20=EC=98=81=EC=97=AD=EC=97=90=20weight?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt index 25e498f2..8885b79d 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt @@ -108,7 +108,7 @@ fun SavedScreen( } } } - Box(modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier .weight(1f) .fillMaxWidth()) { when (selectedTabIndex) { 0 -> FeedContent(feedList = feedList, viewModel = feedViewModel) 1 -> BookContent(bookList = bookList, viewModel = bookViewModel) From 4e8db9d1f9b2b687af775ad4ad2e3baf065f2348 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 17:29:41 +0900 Subject: [PATCH 18/35] =?UTF-8?q?[feat]:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=9A=94=EC=B2=AD,=20=EC=9D=91=EB=8B=B5=20dto=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/data/model/users/NicknameRequest.kt | 8 ++++++++ .../texthip/thip/data/model/users/NicknameResponse.kt | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt b/app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt new file mode 100644 index 00000000..706b30a5 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt @@ -0,0 +1,8 @@ +package com.texthip.thip.data.model.users + +import kotlinx.serialization.Serializable + +@Serializable +data class NicknameRequest( + val nickname: String +) diff --git a/app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt new file mode 100644 index 00000000..287f5a7b --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.users + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NicknameResponse( + @SerialName("isVerified") val isVerified: Boolean +) From 4814787a85fbc21e3611f8fd5af39115d769c11d Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 17:29:57 +0900 Subject: [PATCH 19/35] =?UTF-8?q?[feat]:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20service,=20repository=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../texthip/thip/data/repository/UserRepository.kt | 4 ++++ .../java/com/texthip/thip/data/service/UserService.kt | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index 10099e28..71ee3a75 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -3,6 +3,7 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse import com.texthip.thip.data.model.users.MyFollowingsResponse import com.texthip.thip.data.model.users.MyPageInfoResponse +import com.texthip.thip.data.model.users.NicknameRequest import com.texthip.thip.data.model.users.OthersFollowersResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject @@ -39,4 +40,7 @@ class UserRepository@Inject constructor( .handleBaseResponse() .getOrThrow() } + + suspend fun checkNickname(nickname: String) = + userService.checkNickname(NicknameRequest(nickname)) } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt index 0c50e37c..1a004f61 100644 --- a/app/src/main/java/com/texthip/thip/data/service/UserService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -1,10 +1,16 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse +import com.texthip.thip.data.model.group.request.RoomJoinRequest +import com.texthip.thip.data.model.group.response.RoomJoinResponse import com.texthip.thip.data.model.users.MyFollowingsResponse import com.texthip.thip.data.model.users.MyPageInfoResponse +import com.texthip.thip.data.model.users.NicknameRequest +import com.texthip.thip.data.model.users.NicknameResponse import com.texthip.thip.data.model.users.OthersFollowersResponse +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query @@ -24,4 +30,9 @@ interface UserService { @GET("users/my-page") suspend fun getMyPage(): BaseResponse + + @POST("users/nickname") + suspend fun checkNickname( + @Body request: NicknameRequest + ): BaseResponse } \ No newline at end of file From ddd4bf95483acadef4b637773d60df64a7e26dc2 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 18:31:40 +0900 Subject: [PATCH 20/35] =?UTF-8?q?[feat]:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=83=9D=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/repository/UserRepository.kt | 8 +- .../ui/signin/viewmodel/NicknameViewModel.kt | 81 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index 71ee3a75..40a18f60 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -4,13 +4,14 @@ import com.texthip.thip.data.model.base.handleBaseResponse import com.texthip.thip.data.model.users.MyFollowingsResponse import com.texthip.thip.data.model.users.MyPageInfoResponse import com.texthip.thip.data.model.users.NicknameRequest +import com.texthip.thip.data.model.users.NicknameResponse import com.texthip.thip.data.model.users.OthersFollowersResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject import javax.inject.Singleton @Singleton -class UserRepository@Inject constructor( +class UserRepository @Inject constructor( private val userService: UserService ) { //내 팔로잉 목록 조회 @@ -41,6 +42,9 @@ class UserRepository@Inject constructor( .getOrThrow() } - suspend fun checkNickname(nickname: String) = + suspend fun checkNickname(nickname: String): Result = runCatching { userService.checkNickname(NicknameRequest(nickname)) + .handleBaseResponse() + .getOrThrow() + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt new file mode 100644 index 00000000..fd9b1a90 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt @@ -0,0 +1,81 @@ +package com.texthip.thip.ui.signin.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.R +import com.texthip.thip.data.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + + +data class NicknameUiState( + val isLoading: Boolean = false, + val nickname: String = "", + val isVerified: Boolean? = null, + val warningMessageResId: Int? = null, + val errorMessage: String? = null, + val navigateToNext: Boolean = false +) + +@HiltViewModel +class NicknameViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + private val _uiState = MutableStateFlow(NicknameUiState()) + val uiState = _uiState.asStateFlow() + + fun onNicknameChange(nickname: String) { + // 닉네임 입력 시, 경고 메시지 초기화 + _uiState.update { + it.copy( + nickname = nickname, + warningMessageResId = null + ) + } + } + + fun checkNickname() { + if (_uiState.value.isLoading) return + if (_uiState.value.nickname.isBlank()) return + + viewModelScope.launch { + _uiState.update { + it.copy( + isLoading = true, + warningMessageResId = null, + errorMessage = null + ) + } + + userRepository.checkNickname(_uiState.value.nickname) + .onSuccess { response -> + if (response?.isVerified == true) { + _uiState.update { it.copy(isLoading = false, navigateToNext = true) } + } else { + // 중복된 닉네임 + _uiState.update { + it.copy( + isLoading = false, + warningMessageResId = R.string.nickname_warning + ) + } + } + } + .onFailure { exception -> + _uiState.update { + it.copy( + isLoading = false, + errorMessage = exception.message ?: "알 수 없는 오류가 발생했습니다." + ) + } + } + } + } + fun onNavigated() { + _uiState.update { it.copy(navigateToNext = false, errorMessage = null) } + } +} \ No newline at end of file From 122c4d5635a3eb4670d2f46c96243a9a170511be Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 18:31:54 +0900 Subject: [PATCH 21/35] =?UTF-8?q?[feat]:=20=EB=8B=89=EB=84=A4=EC=9E=84=20s?= =?UTF-8?q?creen,=20content=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=A0=81=EC=9A=A9=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/signin/screen/SignupNicknameScreen.kt | 107 ++++++++++++------ 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt b/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt index 7c41dfd3..a844de6f 100644 --- a/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt @@ -1,5 +1,6 @@ package com.texthip.thip.ui.signin.screen +import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -9,32 +10,61 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.texthip.thip.R import com.texthip.thip.ui.common.forms.WarningTextField import com.texthip.thip.ui.common.topappbar.InputTopAppBar +import com.texthip.thip.ui.signin.viewmodel.NicknameViewModel import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch @Composable -fun SigninNicknameScreen() { - var nickname by rememberSaveable { mutableStateOf("") } - var showWarning by remember { mutableStateOf(false) } - var warningMessageResId by remember { mutableStateOf(null) } - val isRightButtonEnabled by remember {derivedStateOf {nickname.isNotBlank()}} // 닉네임 공백 아닐때 버튼 활성화 - val coroutineScope = rememberCoroutineScope() +fun SignupNicknameScreen( + viewModel: NicknameViewModel = hiltViewModel(), + onNavigateToNext: () -> Unit +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current + + LaunchedEffect(uiState.navigateToNext, uiState.errorMessage) { + if (uiState.navigateToNext) { + onNavigateToNext() + viewModel.onNavigated() + } + uiState.errorMessage?.let { message -> + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + viewModel.onNavigated() + } + } + + SignupNicknameContent( + nickname = uiState.nickname, + onNicknameChange = viewModel::onNicknameChange, + onNextClick = viewModel::checkNickname, + isLoading = uiState.isLoading, + warningMessageResId = uiState.warningMessageResId + ) +} +@Composable +fun SignupNicknameContent( + nickname: String, + onNicknameChange: (String) -> Unit, + onNextClick: () -> Unit, + isLoading: Boolean, + warningMessageResId: Int? +) { + val isRightButtonEnabled = nickname.isNotBlank() && !isLoading // 닉네임 공백 아닐때 버튼 활성화 Column( Modifier @@ -47,20 +77,7 @@ fun SigninNicknameScreen() { rightButtonName = stringResource(R.string.next), isLeftIconVisible = false, onLeftClick = {}, - onRightClick = { - //TODO 서버 연동시 로직 변경 필요 - coroutineScope.launch { - delay(500) // 서버 응답 시뮬레이션 - if (nickname == "test") { - showWarning = true - warningMessageResId = R.string.nickname_warning - } else { - showWarning = false - warningMessageResId = null - // 다음 페이지로 이동 - } - } - } + onRightClick = onNextClick ) Spacer(modifier = Modifier.height(40.dp)) Column( @@ -79,12 +96,9 @@ fun SigninNicknameScreen() { WarningTextField( containerColor = colors.DarkGrey02, value = nickname, - onValueChange = { - nickname = it - showWarning = false // 입력 중에는 경고 숨기기 - }, + onValueChange = onNicknameChange, hint = stringResource(R.string.nickname_condition), - showWarning = showWarning, + showWarning = warningMessageResId != null, showIcon = false, showLimit = true, maxLength = 10, @@ -96,6 +110,33 @@ fun SigninNicknameScreen() { @Preview @Composable -private fun SigninNicknameScreenPrev() { - SigninNicknameScreen() -} \ No newline at end of file +private fun SignupNicknameContentPrev() { + SignupNicknameContent( + nickname = "중복된닉네임", + onNicknameChange = {}, + onNextClick = {}, + isLoading = false, + warningMessageResId = R.string.nickname_warning + ) +} +@Preview(name = "일반 상태 (비어있음)", showBackground = true) +@Composable +private fun SignupNicknameContentPreview_Normal() { + var nickname by remember { mutableStateOf("") } + var warningMessageResId by remember { mutableStateOf(null) } + + SignupNicknameContent( + nickname = nickname, + onNicknameChange = { + nickname = it + warningMessageResId = null + }, + onNextClick = { + if (nickname == "test") { + warningMessageResId = R.string.nickname_warning + } + }, + isLoading = false, + warningMessageResId = warningMessageResId + ) +} From 4801c3408b38ec5f0a0152d0de76a7709871c77b Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:03:52 +0900 Subject: [PATCH 22/35] =?UTF-8?q?[refactor]:=20users=20dto=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=A0=95=EB=A6=AC(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/model/users/{ => request}/NicknameRequest.kt | 4 ++-- .../data/model/users/{ => response}/MyFollowingsResponse.kt | 2 +- .../data/model/users/{ => response}/MyPageInfoResponse.kt | 4 ++-- .../thip/data/model/users/{ => response}/NicknameResponse.kt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename app/src/main/java/com/texthip/thip/data/model/users/{ => request}/NicknameRequest.kt (68%) rename app/src/main/java/com/texthip/thip/data/model/users/{ => response}/MyFollowingsResponse.kt (93%) rename app/src/main/java/com/texthip/thip/data/model/users/{ => response}/MyPageInfoResponse.kt (87%) rename app/src/main/java/com/texthip/thip/data/model/users/{ => response}/NicknameResponse.kt (78%) diff --git a/app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt b/app/src/main/java/com/texthip/thip/data/model/users/request/NicknameRequest.kt similarity index 68% rename from app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt rename to app/src/main/java/com/texthip/thip/data/model/users/request/NicknameRequest.kt index 706b30a5..b0ad1b79 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/NicknameRequest.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/request/NicknameRequest.kt @@ -1,8 +1,8 @@ -package com.texthip.thip.data.model.users +package com.texthip.thip.data.model.users.request import kotlinx.serialization.Serializable @Serializable data class NicknameRequest( val nickname: String -) +) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/model/users/MyFollowingsResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt similarity index 93% rename from app/src/main/java/com/texthip/thip/data/model/users/MyFollowingsResponse.kt rename to app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt index 91807af3..679e555d 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/MyFollowingsResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt @@ -1,4 +1,4 @@ -package com.texthip.thip.data.model.users +package com.texthip.thip.data.model.users.response import com.google.gson.annotations.SerializedName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/MyPageInfoResponse.kt similarity index 87% rename from app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt rename to app/src/main/java/com/texthip/thip/data/model/users/response/MyPageInfoResponse.kt index 76d86893..51acc39f 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/MyPageInfoResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/MyPageInfoResponse.kt @@ -1,4 +1,4 @@ -package com.texthip.thip.data.model.users +package com.texthip.thip.data.model.users.response import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -9,4 +9,4 @@ data class MyPageInfoResponse( @SerialName("nickname") val nickname: String, @SerialName("aliasName") val aliasName: String, @SerialName("aliasColor") val aliasColor: String -) +) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/NicknameResponse.kt similarity index 78% rename from app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt rename to app/src/main/java/com/texthip/thip/data/model/users/response/NicknameResponse.kt index 287f5a7b..3dfab011 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/NicknameResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/NicknameResponse.kt @@ -1,4 +1,4 @@ -package com.texthip.thip.data.model.users +package com.texthip.thip.data.model.users.response import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable From 0cc22ee891e356173a0b308e74b6a20a4e5fab09 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:04:33 +0900 Subject: [PATCH 23/35] =?UTF-8?q?[refactor]:=20users=20dto=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=A0=95=EB=A6=AC(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/repository/UserRepository.kt | 17 ++++++++++++----- .../texthip/thip/data/service/UserService.kt | 16 +++++++++------- .../ui/feed/screen/MySubscriptionListScreen.kt | 2 +- .../feed/screen/OthersSubscriptionListScreen.kt | 2 +- .../feed/viewmodel/MySubscriptionViewModel.kt | 2 +- .../viewmodel/OthersSubscriptionViewModel.kt | 2 +- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index 40a18f60..3207e2b4 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -1,11 +1,12 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse -import com.texthip.thip.data.model.users.MyFollowingsResponse -import com.texthip.thip.data.model.users.MyPageInfoResponse -import com.texthip.thip.data.model.users.NicknameRequest -import com.texthip.thip.data.model.users.NicknameResponse -import com.texthip.thip.data.model.users.OthersFollowersResponse +import com.texthip.thip.data.model.users.response.MyFollowingsResponse +import com.texthip.thip.data.model.users.response.MyPageInfoResponse +import com.texthip.thip.data.model.users.request.NicknameRequest +import com.texthip.thip.data.model.users.response.AliasChoiceResponse +import com.texthip.thip.data.model.users.response.NicknameResponse +import com.texthip.thip.data.model.users.response.OthersFollowersResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject import javax.inject.Singleton @@ -47,4 +48,10 @@ class UserRepository @Inject constructor( .handleBaseResponse() .getOrThrow() } + + suspend fun getAliasChoices(): Result = runCatching { + userService.getAliasChoices() + .handleBaseResponse() + .getOrThrow() + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt index 1a004f61..1a00b37d 100644 --- a/app/src/main/java/com/texthip/thip/data/service/UserService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -1,13 +1,12 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse -import com.texthip.thip.data.model.group.request.RoomJoinRequest -import com.texthip.thip.data.model.group.response.RoomJoinResponse -import com.texthip.thip.data.model.users.MyFollowingsResponse -import com.texthip.thip.data.model.users.MyPageInfoResponse -import com.texthip.thip.data.model.users.NicknameRequest -import com.texthip.thip.data.model.users.NicknameResponse -import com.texthip.thip.data.model.users.OthersFollowersResponse +import com.texthip.thip.data.model.users.response.MyFollowingsResponse +import com.texthip.thip.data.model.users.response.MyPageInfoResponse +import com.texthip.thip.data.model.users.request.NicknameRequest +import com.texthip.thip.data.model.users.response.AliasChoiceResponse +import com.texthip.thip.data.model.users.response.NicknameResponse +import com.texthip.thip.data.model.users.response.OthersFollowersResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -35,4 +34,7 @@ interface UserService { suspend fun checkNickname( @Body request: NicknameRequest ): BaseResponse + + @GET("users/alias") // 실제 API 경로로 수정해주세요. + suspend fun getAliasChoices(): BaseResponse } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt index e3404c90..aa2f08b5 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.texthip.thip.R -import com.texthip.thip.data.model.users.FollowingList +import com.texthip.thip.data.model.users.response.FollowingList import com.texthip.thip.ui.common.header.AuthorHeader import com.texthip.thip.ui.common.modal.ToastWithDate import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt index de305dba..d34d19af 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/OthersSubscriptionListScreen.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.texthip.thip.R -import com.texthip.thip.data.model.users.FollowerList +import com.texthip.thip.data.model.users.response.FollowerList import com.texthip.thip.ui.common.header.AuthorHeader import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar import com.texthip.thip.ui.feed.viewmodel.OthersSubscriptionUiState diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt index 7f4f03c2..9306c96e 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt @@ -2,7 +2,7 @@ package com.texthip.thip.ui.feed.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.texthip.thip.data.model.users.FollowingList +import com.texthip.thip.data.model.users.response.FollowingList import com.texthip.thip.data.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt index ef38b5d0..ae6bc2f7 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/OthersSubscriptionViewModel.kt @@ -3,7 +3,7 @@ package com.texthip.thip.ui.feed.viewmodel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.texthip.thip.data.model.users.FollowerList +import com.texthip.thip.data.model.users.response.FollowerList import com.texthip.thip.data.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow From 24f93c5d1447696b6cde85b99a9914943bcdefc8 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:05:04 +0900 Subject: [PATCH 24/35] =?UTF-8?q?[feat]:=20=EC=9E=A5=EB=A5=B4=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=9D=91=EB=8B=B5dto=20=EC=83=9D=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/users/response/AliasChoiceResponse.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/response/AliasChoiceResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/AliasChoiceResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/AliasChoiceResponse.kt new file mode 100644 index 00000000..b4753a6f --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/AliasChoiceResponse.kt @@ -0,0 +1,17 @@ +package com.texthip.thip.data.model.users.response + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class AliasChoiceResponse( + @SerializedName("aliasChoices") val aliasChoices: List +) + +@Serializable +data class AliasChoice( + @SerializedName("aliasName") val aliasName: String, + @SerializedName("categoryName") val categoryName: String, + @SerializedName("imageUrl") val imageUrl: String, + @SerializedName("aliasColor") val aliasColor: String +) \ No newline at end of file From 1f236b71fb0d2338a5baeba68f117ba9c791578b Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:27:23 +0900 Subject: [PATCH 25/35] =?UTF-8?q?[feat]:=20roleitem=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80,=20=EC=BB=AC=EB=9F=AC=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/component/RoleCard.kt | 37 +++++++------------ .../texthip/thip/ui/mypage/mock/RoleItem.kt | 6 +-- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/component/RoleCard.kt b/app/src/main/java/com/texthip/thip/ui/mypage/component/RoleCard.kt index 5e1ea662..58772100 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/component/RoleCard.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/component/RoleCard.kt @@ -1,6 +1,5 @@ package com.texthip.thip.ui.mypage.component -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.shape.RoundedCornerShape import androidx.compose.material3.Text @@ -22,30 +20,24 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.texthip.thip.R +import coil.compose.AsyncImage import com.texthip.thip.ui.theme.DarkGrey -import com.texthip.thip.ui.theme.NeonGreen -import com.texthip.thip.ui.theme.Art import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography import com.texthip.thip.ui.theme.White +import com.texthip.thip.utils.color.hexToColor @Composable fun RoleCard( modifier: Modifier = Modifier, genre: String, role: String, - imageResId: Int, - genreColor: Color, - roleColor: Color, + imageUrl: String, + roleColor: String, selected: Boolean, onClick: () -> Unit ) { @@ -56,7 +48,7 @@ fun RoleCard( } else { colors.Black700 } - val actualGenreColor = if (selected) genreColor else colors.Grey01 + val actualGenreColor = if (selected) colors.White else colors.Grey01 Box( modifier = modifier .width(162.dp) @@ -70,12 +62,11 @@ fun RoleCard( ) .clickable { onClick() } ) { - Image( - painter = painterResource(id = imageResId), + AsyncImage( + model = imageUrl, contentDescription = null, modifier = Modifier - .align(Alignment.BottomStart) - .size(80.dp), + .align(Alignment.BottomStart), contentScale = ContentScale.Fit, alpha = bgAlpha ) @@ -93,7 +84,7 @@ fun RoleCard( Text( text = role, style = typography.info_r400_s12, - color = roleColor + color = hexToColor(roleColor) ) } } @@ -113,9 +104,8 @@ fun RoleCardPreview() { RoleCard( genre = "문학", role = "문학가", - imageResId = R.drawable.character_literature, - genreColor = White, - roleColor = NeonGreen, + imageUrl = "https://picsum.photos/200", + roleColor = "#A0E931", selected = selected1, onClick = { selected1 = !selected1 } ) @@ -123,9 +113,8 @@ fun RoleCardPreview() { RoleCard( genre = "예술", role = "예술가", - imageResId = R.drawable.character_art, - genreColor = White, - roleColor = Art, + imageUrl = "https://picsum.photos/200", + roleColor = "#A00000", selected = selected2, onClick = { selected2 = !selected2 } ) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/mock/RoleItem.kt b/app/src/main/java/com/texthip/thip/ui/mypage/mock/RoleItem.kt index ce193f5f..3801187b 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/mock/RoleItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/mock/RoleItem.kt @@ -1,10 +1,8 @@ package com.texthip.thip.ui.mypage.mock -import androidx.compose.ui.graphics.Color - data class RoleItem( val genre: String, val role: String, - val imageResId: Int, - val roleColor: Color + val imageUrl: String, + val roleColor: String ) From 5be875db969a97aa905f837fb03a386723e8c18a Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:27:42 +0900 Subject: [PATCH 26/35] =?UTF-8?q?[feat]:=20roleitem=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80,=20=EC=BB=AC=EB=9F=AC=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/screen/MypageEditScreen.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt index e4d5b5d6..97e661a4 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -37,32 +38,32 @@ fun EditProfileScreen() { RoleItem( stringResource(R.string.literature), stringResource(R.string.literary_person), - R.drawable.character_literature, - colors.Literature + "https://photos/1111", + "#FF6B6B" ), RoleItem( stringResource(R.string.science_it), stringResource(R.string.scientist), - R.drawable.character_science, - colors.ScienceIt + "https://photos/1111", + "#FF6B6B" ), RoleItem( stringResource(R.string.social_science), stringResource(R.string.sociologist), - R.drawable.character_sociology, - colors.SocialScience + "https://photos/1111", + "#FF6B6B" ), RoleItem( stringResource(R.string.art), stringResource(R.string.artist), - R.drawable.character_art, - colors.Art + "https://photos/1111", + "#FF6B6B" ), RoleItem( stringResource(R.string.humanities), stringResource(R.string.philosopher), - R.drawable.character_humanities, - colors.Humanities + "https://photos/1111", + "#FF6B6B" ) ) Column( @@ -134,13 +135,12 @@ fun EditProfileScreen() { verticalArrangement = Arrangement.spacedBy(16.dp), userScrollEnabled = false, ) { - items(roleCards.size) { index -> + itemsIndexed(roleCards) { index, roleItem -> RoleCard( - genre = roleCards[index].genre, - role = roleCards[index].role, - imageResId = roleCards[index].imageResId, - genreColor = colors.White, - roleColor = roleCards[index].roleColor, + genre = roleItem.genre, + role = roleItem.role, + imageUrl = roleItem.imageUrl, + roleColor = roleItem.roleColor, selected = selectedIndex == index, onClick = { selectedIndex = index } ) From bd1884a9664932b2c7065c634d11611549f6fdca Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:28:02 +0900 Subject: [PATCH 27/35] =?UTF-8?q?[feat]:=20=EC=9E=A5=EB=A5=B4=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../signin/viewmodel/SignupAliasViewModel.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt diff --git a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt new file mode 100644 index 00000000..0f0b7ab5 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt @@ -0,0 +1,59 @@ +package com.texthip.thip.ui.signin.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.repository.UserRepository +import com.texthip.thip.ui.mypage.mock.RoleItem +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlin.onSuccess + +data class SignupAliasUiState( + val isLoading: Boolean = false, + val roleCards: List = emptyList(), + val selectedIndex: Int = -1, + val errorMessage: String? = null +) + +@HiltViewModel +class SignupAliasViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(SignupAliasUiState()) + val uiState = _uiState.asStateFlow() + + init { + fetchAliasChoices() + } + + fun fetchAliasChoices() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + userRepository.getAliasChoices() + .onSuccess { response -> + val roleCards = response?.aliasChoices?.map { aliasChoice -> + RoleItem( + genre = aliasChoice.aliasName, + role = aliasChoice.categoryName, + imageUrl = aliasChoice.imageUrl, + roleColor = aliasChoice.aliasColor + ) + } ?: emptyList() + _uiState.update { it.copy(isLoading = false, roleCards = roleCards) } + } + .onFailure { exception -> + _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } + } + } + } + + fun selectCard(index: Int) { + _uiState.update { it.copy(selectedIndex = index) } + } +} + From 2667493c78949b174d7f304d46ee5f3463a8f5ad Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Thu, 14 Aug 2025 19:28:26 +0900 Subject: [PATCH 28/35] =?UTF-8?q?[feat]:=20=EC=9E=A5=EB=A5=B4=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20screen,=20content=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=A0=81=EC=9A=A9=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/signin/screen/SignupGenreScreen.kt | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt b/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt index 26efed99..30f06578 100644 --- a/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt @@ -9,64 +9,51 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.texthip.thip.R import com.texthip.thip.ui.common.topappbar.InputTopAppBar import com.texthip.thip.ui.mypage.component.RoleCard import com.texthip.thip.ui.mypage.mock.RoleItem +import com.texthip.thip.ui.signin.viewmodel.SignupAliasUiState +import com.texthip.thip.ui.signin.viewmodel.SignupAliasViewModel import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography - @Composable -fun SignupGenreScreen() { - var selectedIndex by rememberSaveable { mutableStateOf(-1) } - val isRightButtonEnabled by remember { derivedStateOf { selectedIndex != -1 } } +fun SignupGenreScreen( + onNavigateToNext: (RoleItem) -> Unit, // 선택된 아이템 정보를 다음 화면으로 넘겨주기 + viewModel: SignupAliasViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() - val roleCards = listOf( - RoleItem( - stringResource(R.string.literature), - stringResource(R.string.literary_person), - R.drawable.character_literature, - colors.Literature - ), - RoleItem( - stringResource(R.string.science_it), - stringResource(R.string.scientist), - R.drawable.character_science, - colors.ScienceIt - ), - RoleItem( - stringResource(R.string.social_science), - stringResource(R.string.sociologist), - R.drawable.character_sociology, - colors.SocialScience - ), - RoleItem( - stringResource(R.string.art), - stringResource(R.string.artist), - R.drawable.character_art, - colors.Art - ), - RoleItem( - stringResource(R.string.humanities), - stringResource(R.string.philosopher), - R.drawable.character_humanities, - colors.Humanities - ) + SignupGenreContent( + uiState = uiState, + onCardSelected = { index -> viewModel.selectCard(index) }, + onNextClick = { + // 선택된 아이템이 있을 경우에만 다음 화면으로 이동 + uiState.roleCards.getOrNull(uiState.selectedIndex)?.let { selectedRoleItem -> + onNavigateToNext(selectedRoleItem) + } + } ) +} +@Composable +fun SignupGenreContent( + uiState: SignupAliasUiState, + onCardSelected: (Int) -> Unit, + onNextClick: () -> Unit +) { + val isRightButtonEnabled = uiState.selectedIndex != -1 && !uiState.isLoading Column( Modifier @@ -78,9 +65,7 @@ fun SignupGenreScreen() { rightButtonName = stringResource(R.string.next), isLeftIconVisible = false, onLeftClick = {}, - onRightClick = { - // TODO 다음 화면으로 이동 - } + onRightClick = onNextClick ) Spacer(modifier = Modifier.height(40.dp)) @@ -114,15 +99,14 @@ fun SignupGenreScreen() { verticalArrangement = Arrangement.spacedBy(16.dp), userScrollEnabled = false, ) { - items(roleCards.size) { index -> + itemsIndexed(uiState.roleCards) { index, roleItem -> RoleCard( - genre = roleCards[index].genre, - role = roleCards[index].role, - imageResId = roleCards[index].imageResId, - genreColor = colors.White, - roleColor = roleCards[index].roleColor, - selected = selectedIndex == index, - onClick = { selectedIndex = index } + genre = roleItem.genre, + role = roleItem.role, + imageUrl = roleItem.imageUrl, + roleColor = roleItem.roleColor, + selected = uiState.selectedIndex == index, + onClick = { onCardSelected(index) } ) } } @@ -134,7 +118,23 @@ fun SignupGenreScreen() { @Preview @Composable private fun SignupGenreScreenPrev() { + val previewRoleCards = listOf( + RoleItem("문학", "문학가", "", "#FFFFFF"), + RoleItem("과학/IT", "과학자", "", "#FFFFFF"), + RoleItem("사회", "사회학자", "", "#FFFFFF"), + RoleItem("예술", "예술가", "", "#FFFFFF"), + RoleItem("인문", "철학자", "", "#FFFFFF") + ) + val previewUiState = SignupAliasUiState( + roleCards = previewRoleCards, + selectedIndex = 1 // 1번 아이템이 선택된 상태로 프리뷰 + ) + ThipTheme { - SignupGenreScreen() + SignupGenreContent( + uiState = previewUiState, + onCardSelected = {}, + onNextClick = {} + ) } } \ No newline at end of file From a94d14d06f16a74abc9953b09a16dab1a8acea2a Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 02:40:50 +0900 Subject: [PATCH 29/35] =?UTF-8?q?[feat]:=20=EB=82=B4=20=ED=8C=94=EB=A1=9C?= =?UTF-8?q?=EC=9E=89=20=EC=B5=9C=EA=B7=BC=20=ED=94=BC=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90=EC=88=9C=EC=9C=BC=EB=A1=9C=20-=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20dto=20=EC=83=9D=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/users/response/MyFollowingsResponse.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt index 679e555d..21b395df 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt @@ -13,10 +13,22 @@ data class MyFollowingsResponse( @Serializable data class FollowingList( - @SerializedName("userId") val userId: Int, + @SerializedName("userId") val userId: Long, @SerializedName("nickname") val nickname: String, @SerializedName("profileImageUrl") val profileImageUrl: String?, @SerializedName("aliasName") val aliasName: String, @SerializedName("aliasColor") val aliasColor: String, @SerializedName("isFollowing") val isFollowing: Boolean ) + +@Serializable +data class MyRecentFollowingsResponse( + @SerializedName("recentWriters") val recentWriters: List +) + +@Serializable +data class RecentWriterList( + @SerializedName("userId") val userId: Long, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String? +) \ No newline at end of file From 043a06d74ca7035c386d4cf17c7904e3d2fc1792 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 02:41:32 +0900 Subject: [PATCH 30/35] =?UTF-8?q?[feat]:=20=EB=82=B4=20=ED=8C=94=EB=A1=9C?= =?UTF-8?q?=EC=9E=89=20=EC=B5=9C=EA=B7=BC=20=ED=94=BC=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90=EC=88=9C=EC=9C=BC=EB=A1=9C=20-=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=84=9C=EB=B9=84=EC=8A=A4,=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=88=98=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/data/repository/UserRepository.kt | 9 +++++++++ .../java/com/texthip/thip/data/service/UserService.kt | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index 3207e2b4..c21feed9 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -1,10 +1,13 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse +import com.texthip.thip.data.model.users.request.FollowRequest import com.texthip.thip.data.model.users.response.MyFollowingsResponse import com.texthip.thip.data.model.users.response.MyPageInfoResponse import com.texthip.thip.data.model.users.request.NicknameRequest import com.texthip.thip.data.model.users.response.AliasChoiceResponse +import com.texthip.thip.data.model.users.response.FollowResponse +import com.texthip.thip.data.model.users.response.MyRecentFollowingsResponse import com.texthip.thip.data.model.users.response.NicknameResponse import com.texthip.thip.data.model.users.response.OthersFollowersResponse import com.texthip.thip.data.service.UserService @@ -25,6 +28,12 @@ class UserRepository @Inject constructor( .getOrThrow() } + suspend fun getRecentWriters(): Result = runCatching { + userService.getRecentWriters() + .handleBaseResponse() + .getOrThrow() + } + //다른 유저 팔로워 목록 조회 suspend fun getOthersFollowers( userId: Long, diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt index 1a00b37d..6f2f695e 100644 --- a/app/src/main/java/com/texthip/thip/data/service/UserService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -1,10 +1,13 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse +import com.texthip.thip.data.model.users.request.FollowRequest import com.texthip.thip.data.model.users.response.MyFollowingsResponse import com.texthip.thip.data.model.users.response.MyPageInfoResponse import com.texthip.thip.data.model.users.request.NicknameRequest import com.texthip.thip.data.model.users.response.AliasChoiceResponse +import com.texthip.thip.data.model.users.response.FollowResponse +import com.texthip.thip.data.model.users.response.MyRecentFollowingsResponse import com.texthip.thip.data.model.users.response.NicknameResponse import com.texthip.thip.data.model.users.response.OthersFollowersResponse import retrofit2.http.Body @@ -20,6 +23,9 @@ interface UserService { @Query("cursor") cursor: String? = null ): BaseResponse + @GET("users/my-followings/recent-feeds") + suspend fun getRecentWriters(): BaseResponse + @GET("users/{userId}/followers") suspend fun getUserFollowers( @Path("userId") userId: Long, @@ -35,6 +41,6 @@ interface UserService { @Body request: NicknameRequest ): BaseResponse - @GET("users/alias") // 실제 API 경로로 수정해주세요. + @GET("users/alias") suspend fun getAliasChoices(): BaseResponse } \ No newline at end of file From 48addb786ee716ac746f46b0cd8e85c89556eeb6 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 02:42:44 +0900 Subject: [PATCH 31/35] =?UTF-8?q?[feat]:=20=EB=82=B4=20=ED=8C=94=EB=A1=9C?= =?UTF-8?q?=EC=9E=89=20=EC=B5=9C=EA=B7=BC=20=ED=94=BC=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90=EC=88=9C=EC=9C=BC=EB=A1=9C=20-=20FeedScreen?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/ui/feed/screen/FeedScreen.kt | 14 +++++++------- .../ui/feed/screen/MySubscriptionListScreen.kt | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt index c4ff2cfb..218c0a4c 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt @@ -14,11 +14,11 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -36,10 +36,9 @@ import com.texthip.thip.ui.feed.component.FeedSubscribeBarlist import com.texthip.thip.ui.feed.component.MyFeedCard import com.texthip.thip.ui.feed.component.MySubscribeBarlist import com.texthip.thip.ui.feed.mock.MySubscriptionData -import com.texthip.thip.ui.feed.viewmodel.MySubscriptionViewModel +import com.texthip.thip.ui.feed.viewmodel.FeedViewModel import com.texthip.thip.ui.mypage.component.SavedFeedCard import com.texthip.thip.ui.mypage.mock.FeedItem -import com.texthip.thip.ui.navigator.routes.FeedRoutes import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @@ -55,8 +54,9 @@ fun FeedScreen( totalFeedCount: Int = 0, selectedTabIndex: Int = 0, followerProfileImageUrls: List = emptyList(), - viewModel: MySubscriptionViewModel = hiltViewModel() + viewModel: FeedViewModel = hiltViewModel() ) { + val feedUiState by viewModel.uiState.collectAsState() val selectedIndex = rememberSaveable { mutableIntStateOf(selectedTabIndex) } val feedStateList = remember { mutableStateListOf().apply { @@ -213,14 +213,14 @@ fun FeedScreen( //피드 item { Spacer(modifier = Modifier.height(20.dp)) - val subscriptionsForBar = subscriptionUiState.followings.map { user -> + val subscriptionsForBar = feedUiState.recentWriters.map { user -> MySubscriptionData( profileImageUrl = user.profileImageUrl, nickname = user.nickname, - role = user.aliasName, + role = "", roleColor = colors.White, subscriberCount = 0, - isSubscribed = user.isFollowing + isSubscribed = true ) } MySubscribeBarlist( diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt index aa2f08b5..c487f967 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt @@ -87,7 +87,7 @@ fun MySubscriptionContent( uiState: MySubscriptionUiState, lazyListState: LazyListState, onNavigateBack: () -> Unit, - onToggleFollow: (userId: Int, nickname: String) -> Unit, + onToggleFollow: (userId: Long, nickname: String) -> Unit, onHideToast: () -> Unit ) { LaunchedEffect(uiState.showToast) { @@ -194,7 +194,7 @@ fun MySubscriptionContent( private fun MySubscriptionListScreenPrev() { val mockUsers = (1..10).map { FollowingList( - userId = it, + userId = it.toLong(), profileImageUrl = null, nickname = "문학소년 $it", aliasName = if (it % 3 == 0) "공식 인플루언서" else "글쓰는 탐험가", From 1b409334556d75afe309b7960091421256807170 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 02:42:59 +0900 Subject: [PATCH 32/35] =?UTF-8?q?[feat]:=20Feed=20=EB=B7=B0=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=83=9D=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/feed/viewmodel/FeedViewModel.kt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt new file mode 100644 index 00000000..04b3bdd5 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt @@ -0,0 +1,50 @@ +package com.texthip.thip.ui.feed.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.model.users.response.RecentWriterList +import com.texthip.thip.data.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class FeedUiState( + val isLoading: Boolean = true, + val recentWriters: List = emptyList(), + val errorMessage: String? = null + //TODO 추후 피드 목록 등 다른 상태들 추가될 예정 +) + +@HiltViewModel +class FeedViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(FeedUiState()) + val uiState = _uiState.asStateFlow() + + init { + fetchRecentWriters() + } + + private fun fetchRecentWriters() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + userRepository.getRecentWriters() + .onSuccess { data -> + _uiState.update { + it.copy( + isLoading = false, + recentWriters = data?.recentWriters ?: emptyList() + ) + } + } + .onFailure { exception -> + _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } + } + } + } +} \ No newline at end of file From d6de6da819ceb4ad7eb8a5ecdc07f320e1cc2f33 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 02:43:21 +0900 Subject: [PATCH 33/35] =?UTF-8?q?[feat]:=20=ED=8C=94=EB=A1=9C=EC=9E=89=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20-=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD,=20=EC=9D=91=EB=8B=B5=20=20dto=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/model/users/request/FollowRequest.kt | 8 ++++++++ .../thip/data/model/users/response/FollowResponse.kt | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/request/FollowRequest.kt create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/response/FollowResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/request/FollowRequest.kt b/app/src/main/java/com/texthip/thip/data/model/users/request/FollowRequest.kt new file mode 100644 index 00000000..7b1c9bda --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/request/FollowRequest.kt @@ -0,0 +1,8 @@ +package com.texthip.thip.data.model.users.request + +import kotlinx.serialization.Serializable + +@Serializable +data class FollowRequest( + val type: Boolean +) diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/FollowResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/FollowResponse.kt new file mode 100644 index 00000000..51dfea5c --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/FollowResponse.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.users.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class FollowResponse( + @SerialName("isFollowing") val isFollowing: Boolean +) From ab12abc831068cebcb70e1cf4e0d292b3dea3dbe Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 02:43:37 +0900 Subject: [PATCH 34/35] =?UTF-8?q?[feat]:=20=ED=8C=94=EB=A1=9C=EC=9E=89=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20-=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC,=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/data/repository/UserRepository.kt | 10 ++++++++++ .../java/com/texthip/thip/data/service/UserService.kt | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt index c21feed9..5cfd6244 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -63,4 +63,14 @@ class UserRepository @Inject constructor( .handleBaseResponse() .getOrThrow() } + + suspend fun toggleFollow( + followingUserId: Long, + isFollowing: Boolean + ): Result = runCatching { + val request = FollowRequest(type = isFollowing) + userService.toggleFollow(followingUserId, request) + .handleBaseResponse() + .getOrThrow() + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt index 6f2f695e..6b2549f7 100644 --- a/app/src/main/java/com/texthip/thip/data/service/UserService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -43,4 +43,10 @@ interface UserService { @GET("users/alias") suspend fun getAliasChoices(): BaseResponse + + @POST("users/following/{followingUserId}") + suspend fun toggleFollow( + @Path("followingUserId") followingUserId: Long, + @Body request: FollowRequest + ): BaseResponse } \ No newline at end of file From ab8093b9e6a99dceeaca82efa76b3e525429790d Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Fri, 15 Aug 2025 03:34:59 +0900 Subject: [PATCH 35/35] =?UTF-8?q?[feat]:=20=EB=82=B4=20=EA=B5=AC=EB=8F=85?= =?UTF-8?q?=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80+=ED=8C=94=EB=A1=9C=EC=9E=89=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EC=A0=81=EC=9A=A9=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/screen/MySubscriptionListScreen.kt | 4 +- .../feed/viewmodel/MySubscriptionViewModel.kt | 58 ++++++++++++++----- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt index c487f967..96e75127 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt @@ -48,7 +48,7 @@ import kotlinx.coroutines.delay @Composable fun MySubscriptionScreen( - navController: NavController, + navController: NavController?= null, viewModel: MySubscriptionViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState() @@ -73,7 +73,7 @@ fun MySubscriptionScreen( MySubscriptionContent( uiState = uiState, lazyListState = lazyListState, - onNavigateBack = { navController.popBackStack() }, + onNavigateBack = { navController?.popBackStack() }, onToggleFollow = { userId, nickname -> val followedMessage = context.getString(R.string.toast_thip, nickname) val unfollowedMessage = context.getString(R.string.toast_thip_cancel, nickname) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt index 9306c96e..3fe21831 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt @@ -71,24 +71,54 @@ class MySubscriptionViewModel @Inject constructor( } - fun toggleFollow(userId: Int, followedMessage: String, unfollowedMessage: String) { - var toastMsg = "" - _uiState.update { currentState -> - val updatedList = currentState.followings.map { user -> - if (user.userId == userId) { - val isNowFollowing = !user.isFollowing - toastMsg = if (isNowFollowing) followedMessage else unfollowedMessage - user.copy(isFollowing = isNowFollowing) - } else { - user - } + fun toggleFollow(userId: Long, followedMessage: String, unfollowedMessage: String) { + val currentState = _uiState.value + val userToUpdate = currentState.followings.find { it.userId == userId } ?: return + val currentIsFollowing = userToUpdate.isFollowing + //낙관적 업데이트 -> ui 먼저 변경 + val newOptimisticList = currentState.followings.map { user -> + if (user.userId == userId) { + user.copy(isFollowing = !currentIsFollowing) + } else { + user } - currentState.copy( - followings = updatedList, + } + + _uiState.update { + it.copy( + followings = newOptimisticList, showToast = true, - toastMessage = toastMsg + toastMessage = if (!currentIsFollowing) followedMessage else unfollowedMessage ) } + viewModelScope.launch { + val requestType = !currentIsFollowing + + userRepository.toggleFollow(followingUserId = userId, isFollowing = requestType) + .onSuccess { response -> + val serverState = response?.isFollowing ?: requestType + _uiState.update { state -> + state.copy(followings = state.followings.map { user -> + if (user.userId == userId) { + user.copy(isFollowing = serverState) + } else { + user + } + }) + } + } + .onFailure { + _uiState.update { state -> + state.copy(followings = state.followings.map { user -> + if (user.userId == userId) { + user.copy(isFollowing = currentIsFollowing) // 원래 상태로 복원 + } else { + user + } + }) + } + } + } } fun hideToast() { _uiState.update { it.copy(showToast = false) }