From 9be681855eeaac14af77279a1a8f26c77036e725 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Sun, 17 Aug 2025 20:24:40 +0900 Subject: [PATCH 01/22] =?UTF-8?q?[feat]:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=9A=94=EC=B2=AD=20dto?= =?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 --- .../thip/ui/mypage/screen/MypageEditScreen.kt | 112 +++++++++++------- 1 file changed, 69 insertions(+), 43 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 97e661a4..fcfae70b 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 @@ -22,59 +22,56 @@ 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.forms.FormTextFieldDefault +import com.texthip.thip.ui.common.forms.WarningTextField 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.mypage.viewmodel.EditProfileUiState +import com.texthip.thip.ui.mypage.viewmodel.EditProfileViewModel +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 EditProfileScreen() { - var selectedIndex by rememberSaveable { mutableStateOf(-1) } - val roleCards = listOf( - RoleItem( - stringResource(R.string.literature), - stringResource(R.string.literary_person), - "https://photos/1111", - "#FF6B6B" - ), - RoleItem( - stringResource(R.string.science_it), - stringResource(R.string.scientist), - "https://photos/1111", - "#FF6B6B" - ), - RoleItem( - stringResource(R.string.social_science), - stringResource(R.string.sociologist), - "https://photos/1111", - "#FF6B6B" - ), - RoleItem( - stringResource(R.string.art), - stringResource(R.string.artist), - "https://photos/1111", - "#FF6B6B" - ), - RoleItem( - stringResource(R.string.humanities), - stringResource(R.string.philosopher), - "https://photos/1111", - "#FF6B6B" - ) +fun EditProfileScreen( + viewModel: EditProfileViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + EditProfileContent( + uiState = uiState, + onNicknameChange = viewModel::onNicknameChange, + onCardSelected = viewModel::selectCard, + onSaveClick = viewModel::saveProfile ) +} + + +@Composable +fun EditProfileContent( + uiState: EditProfileUiState, + onNicknameChange: (String) -> Unit, + onCardSelected: (Int) -> Unit, + onSaveClick: () -> Unit +) { + val isChanged = uiState.nickname != uiState.initialNickname || uiState.selectedIndex != uiState.initialSelectedIndex + val isRightButtonEnabled = isChanged && uiState.nickname.isNotBlank() && !uiState.isLoading + Column( Modifier .fillMaxSize() ) { InputTopAppBar( title = stringResource(R.string.edit_profile), - isRightButtonEnabled = true, + isRightButtonEnabled = isRightButtonEnabled, onLeftClick = {}, - onRightClick = {} + onRightClick = onSaveClick ) Column( modifier = Modifier @@ -92,13 +89,26 @@ fun EditProfileScreen() { .padding(bottom = 12.dp) ) //TODO 컴포넌트 수정 필요 -> text count 추가, boolean 값으로 icon, limit 설정가능하도록 - FormTextFieldDefault( + /* FormTextFieldDefault( + value = uiState.nickname, + onValueChange = onNicknameChange, modifier = Modifier.fillMaxWidth(), showLimit = true, limit = 10, showIcon = false, containerColor = colors.DarkGrey02, hint = stringResource(R.string.change_nickname) + )*/ + WarningTextField( + containerColor = colors.DarkGrey02, + value = uiState.nickname, + onValueChange = onNicknameChange, + hint = stringResource(R.string.nickname_condition), + showWarning = uiState.nicknameWarningMessageResId != null, + showIcon = false, + showLimit = true, + maxLength = 10, + warningMessage = uiState.nicknameWarningMessageResId?.let { stringResource(it) } ?: "" ) Spacer(modifier = Modifier.height(40.dp)) Text( @@ -115,16 +125,16 @@ fun EditProfileScreen() { color = colors.White, modifier = Modifier .fillMaxWidth() - .padding(bottom = 12.dp) + .padding(bottom = 20.dp) ) - Text( + /*Text( text = stringResource(R.string.choice_one), style = typography.info_r400_s12, color = colors.NeonGreen, modifier = Modifier .fillMaxWidth() .padding(bottom = 12.dp) - ) + )*/ LazyVerticalGrid( @@ -135,14 +145,14 @@ fun EditProfileScreen() { verticalArrangement = Arrangement.spacedBy(16.dp), userScrollEnabled = false, ) { - itemsIndexed(roleCards) { index, roleItem -> + itemsIndexed(uiState.roleCards) { index, roleItem -> RoleCard( genre = roleItem.genre, role = roleItem.role, imageUrl = roleItem.imageUrl, roleColor = roleItem.roleColor, - selected = selectedIndex == index, - onClick = { selectedIndex = index } + selected = uiState.selectedIndex == index, + onClick = { onCardSelected(index) } ) } } @@ -151,10 +161,26 @@ fun EditProfileScreen() { } } + @Preview @Composable private fun EditProfileScreenPrev() { + val previewRoleCards = listOf( + RoleItem("문학", "문학가", "", "#FFFFFF"), + RoleItem("과학/IT", "과학자", "", "#FFFFFF") + ) + val previewUiState = EditProfileUiState( + isLoading = false, + roleCards = previewRoleCards, + selectedIndex = 0, + nickname = "기존닉네임" + ) ThipTheme { - EditProfileScreen() + EditProfileContent( + uiState = previewUiState, + onNicknameChange = {}, + onCardSelected = {}, + onSaveClick = {} + ) } } \ No newline at end of file From 3de2c8abe1ed11370f1afe5cff6af2c8bca5d14a Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Sun, 17 Aug 2025 20:24:46 +0900 Subject: [PATCH 02/22] =?UTF-8?q?[feat]:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=9A=94=EC=B2=AD=20dto?= =?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 --- .../data/model/users/request/ProfileUpdateRequest.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt b/app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt new file mode 100644 index 00000000..28f62012 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.users.request + +import kotlinx.serialization.Serializable + +@Serializable +data class ProfileUpdateRequest( + val nickname: String?, + val aliasName: String +) From 74c207401bb3e50b4c769677e874c167991288b6 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Sun, 17 Aug 2025 20:25:43 +0900 Subject: [PATCH 03/22] =?UTF-8?q?[feat]:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=B7=B0=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8,=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=83=9D=EC=84=B1=20-=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=ED=91=9C=EC=8B=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20=ED=95=84?= =?UTF-8?q?=EC=9A=94#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/repository/UserRepository.kt | 7 + .../texthip/thip/data/service/UserService.kt | 7 + .../mypage/viewmodel/MyPageEditViewModel.kt | 142 ++++++++++++++++++ app/src/main/res/values/strings.xml | 5 + 4 files changed, 161 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.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 5cfd6244..9c7cff80 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 @@ -5,6 +5,7 @@ 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.request.ProfileUpdateRequest 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 @@ -73,4 +74,10 @@ class UserRepository @Inject constructor( .handleBaseResponse() .getOrThrow() } + + suspend fun updateProfile(request: ProfileUpdateRequest): Result = runCatching { + userService.updateProfile(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 6b2549f7..d0ad362f 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 @@ -5,6 +5,7 @@ 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.request.ProfileUpdateRequest 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 @@ -12,6 +13,7 @@ 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.PATCH import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query @@ -49,4 +51,9 @@ interface UserService { @Path("followingUserId") followingUserId: Long, @Body request: FollowRequest ): BaseResponse + + @PATCH("users") + suspend fun updateProfile( + @Body request: ProfileUpdateRequest + ): BaseResponse } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt new file mode 100644 index 00000000..99dbc98a --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt @@ -0,0 +1,142 @@ +package com.texthip.thip.ui.mypage.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.R +import com.texthip.thip.data.model.base.ThipApiFailureException +import com.texthip.thip.data.model.users.request.ProfileUpdateRequest +import com.texthip.thip.data.repository.UserRepository +import com.texthip.thip.ui.mypage.mock.RoleItem +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.async +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 EditProfileUiState( + val isLoading: Boolean = true, + val nickname: String = "", + val roleCards: List = emptyList(), + val selectedIndex: Int = -1, + val initialNickname: String = "", + val initialSelectedIndex: Int = -1, + val nicknameWarningMessageResId: Int? = null, + val errorMessage: String? = null, + val isSaveSuccess: Boolean = false +) + +@HiltViewModel +class EditProfileViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(EditProfileUiState()) + val uiState = _uiState.asStateFlow() + + init { + loadInitialProfile() + } + + private fun loadInitialProfile() { + viewModelScope.launch { + val profileDeferred = async { userRepository.getMyPageInfo() } + val aliasDeferred = async { userRepository.getAliasChoices() } + + val profileResult = profileDeferred.await() + val aliasResult = aliasDeferred.await() + + if (profileResult.isSuccess && aliasResult.isSuccess) { + val profileData = profileResult.getOrNull() + val aliasResponse = aliasResult.getOrNull() + + val roleCards = aliasResponse?.aliasChoices?.map { + RoleItem(it.aliasName, it.categoryName, it.imageUrl, it.aliasColor) + } ?: emptyList() + + // 서버에서 받아온 현재 닉네임과 역할을 설정 + val currentNickname = profileData?.nickname ?: "" + val currentAliasName = profileData?.aliasName ?: "" + val currentSelectedIndex = roleCards.indexOfFirst { it.genre == currentAliasName } + + _uiState.update { + it.copy( + isLoading = false, + nickname = currentNickname, + roleCards = roleCards, + selectedIndex = currentSelectedIndex, + initialNickname = currentNickname, + initialSelectedIndex = currentSelectedIndex + ) + } + } else { + // 둘 중 하나라도 실패하면 에러 메시지 표시 + val error = profileResult.exceptionOrNull() ?: aliasResult.exceptionOrNull() + _uiState.update { it.copy(isLoading = false, errorMessage = error?.message) } + } + } + } + + fun onNicknameChange(newNickname: String) { + _uiState.update { + it.copy( + nickname = newNickname, + nicknameWarningMessageResId = null + ) + } + } + + fun selectCard(index: Int) { + _uiState.update { it.copy(selectedIndex = index) } + } + + fun saveProfile() { + viewModelScope.launch { + val currentState = _uiState.value + + val selectedRole = currentState.roleCards.getOrNull(currentState.selectedIndex) + if (selectedRole == null) { + _uiState.update { it.copy(errorMessage = "역할을 선택해주세요.") } + return@launch + } + + val nicknameToSend: String? = + if (currentState.nickname != currentState.initialNickname) { + currentState.nickname + } else { + null + } + + val request = ProfileUpdateRequest( + nickname = nicknameToSend, + aliasName = selectedRole.genre + ) + + userRepository.updateProfile(request) + .onSuccess { + _uiState.update { it.copy(isSaveSuccess = true) } + } + .onFailure { exception -> + if (exception is ThipApiFailureException) { + when (exception.code) { + 70004, 70005, 70006 -> { + val messageResId = when (exception.code) { + 70004 -> R.string.nickname_error_same + 70005 -> R.string.nickname_error_cooldown + else -> R.string.nickname_error_duplicate + } + _uiState.update { it.copy(nicknameWarningMessageResId = messageResId) } + } + + else -> { + _uiState.update { it.copy(errorMessage = exception.message) } + } + } + } else { + _uiState.update { it.copy(errorMessage = "네트워크 오류가 발생했습니다.") } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39ec0b0d..39d84e5c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -133,6 +133,11 @@ 아직 저장한 반응이 없어요 첫 번째 반응을 저장해 보세요! + 닉네임은 6개월에 한 번 변경할 수 있어요 + 중복된 닉네임이에요 + 현재 닉네임과 같아요 + 알 수 없는 오류가 발생했어요 + 진행중 모집중 From fda6aa13750fe1b2459e7be979a129ce0a036c11 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 09:08:20 +0900 Subject: [PATCH 04/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9D=91=EB=8B=B5,=20=EC=9A=94=EC=B2=AD=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 --- .../thip/data/model/users/request/SignupRequest.kt | 9 +++++++++ .../thip/data/model/users/response/SignupResponse.kt | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt b/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt new file mode 100644 index 00000000..32f4d9d3 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.users.request + +import kotlinx.serialization.Serializable + +@Serializable +data class SignupRequest( + val nickName: String, + val aliasName: String +) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt new file mode 100644 index 00000000..5f9167f1 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.users.response + +import kotlinx.serialization.Serializable + +@Serializable +data class SignupResponse( + val accessToken: String, + val refreshToken: String +) \ No newline at end of file From 4098c6df1de789a46f4fa52f3f906d7008fedae0 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 09:08:36 +0900 Subject: [PATCH 05/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=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=EB=A9=94=EC=84=9C=EB=93=9C=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 --- .../thip/data/repository/UserRepository.kt | 17 ++++++++++++++++- .../texthip/thip/data/service/UserService.kt | 9 +++++++++ 2 files changed, 25 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 9c7cff80..bfece035 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,23 +1,27 @@ package com.texthip.thip.data.repository +import com.texthip.thip.data.manager.TokenManager 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.request.ProfileUpdateRequest +import com.texthip.thip.data.model.users.request.SignupRequest 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.model.users.response.SignupResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject import javax.inject.Singleton @Singleton class UserRepository @Inject constructor( - private val userService: UserService + private val userService: UserService, + private val tokenManager: TokenManager ) { //내 팔로잉 목록 조회 suspend fun getMyFollowings( @@ -80,4 +84,15 @@ class UserRepository @Inject constructor( .handleBaseResponse() .getOrThrow() } + + suspend fun signup(request: SignupRequest): Result { + val tempToken = tokenManager.getTempToken() + ?: return Result.failure(Exception("임시 토큰이 없습니다.")) + + return runCatching { + userService.signup("Bearer $tempToken", 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 d0ad362f..90e3f178 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 @@ -6,13 +6,16 @@ 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.request.ProfileUpdateRequest +import com.texthip.thip.data.model.users.request.SignupRequest 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.model.users.response.SignupResponse import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Path @@ -56,4 +59,10 @@ interface UserService { suspend fun updateProfile( @Body request: ProfileUpdateRequest ): BaseResponse + + @POST("users/signup") + suspend fun signup( + @Header("Authorization") tempToken: String, + @Body request: SignupRequest + ): BaseResponse } \ No newline at end of file From 5bb468d1094bca52f823c955a2f13ab437a28c5c Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 09:08:53 +0900 Subject: [PATCH 06/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20-=20=ED=86=A0=ED=81=B0=EB=A7=A4=EB=8B=88=EC=A0=80?= =?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/manager/TokenManager.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt diff --git a/app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt b/app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt new file mode 100644 index 00000000..168bd8b0 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt @@ -0,0 +1,64 @@ +package com.texthip.thip.data.manager + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + + +private val Context.dataStore: DataStore by preferencesDataStore(name = "thip_tokens") + +@Singleton +class TokenManager @Inject constructor( + @ApplicationContext private val context: Context +) { + // 저장할 데이터의 Key 정의 + companion object { + private val TEMP_TOKEN_KEY = stringPreferencesKey("temp_token") + private val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token") + private val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token") + } + + // 임시 토큰 저장 + suspend fun saveTempToken(token: String) { + context.dataStore.edit { prefs -> + prefs[TEMP_TOKEN_KEY] = token + } + } + + // 임시 토큰 읽기 + suspend fun getTempToken(): String? { + return context.dataStore.data.map { prefs -> + prefs[TEMP_TOKEN_KEY] + }.first() // Flow에서 첫 번째 값을 한번만 읽어옴 + } + + // 정식 토큰들(Access, Refresh) 저장 + suspend fun saveAccessTokens(accessToken: String, refreshToken: String) { + context.dataStore.edit { prefs -> + prefs[ACCESS_TOKEN_KEY] = accessToken + prefs[REFRESH_TOKEN_KEY] = refreshToken + } + } + + // Access Token 읽기 (Flow로 제공하여 토큰 변화를 감지할 수 있게 함) + fun getAccessToken(): kotlinx.coroutines.flow.Flow { + return context.dataStore.data.map { prefs -> + prefs[ACCESS_TOKEN_KEY] + } + } + + // 모든 토큰 삭제 (로그아웃 시) + suspend fun clearTokens() { + context.dataStore.edit { prefs -> + prefs.clear() + } + } +} \ No newline at end of file From 65d5f72551ddc4e7e737d3e149f078270734b2a1 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 09:09:36 +0900 Subject: [PATCH 07/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=83=9D=EC=84=B1,?= =?UTF-8?q?=20=EB=8B=89=EB=84=A4=EC=9E=84=EA=B2=80=EC=A6=9D/=20=EC=B9=AD?= =?UTF-8?q?=ED=98=B8=EC=84=A0=ED=83=9D=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC(=EC=9E=84=EC=8B=9C)=20(#73?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/signin/viewmodel/NicknameViewModel.kt | 3 +- .../signin/viewmodel/SignupAliasViewModel.kt | 2 + .../ui/signin/viewmodel/SignupViewModel.kt | 114 ++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt 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 index fd9b1a90..1d098c26 100644 --- 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 @@ -1,3 +1,4 @@ +/* package com.texthip.thip.ui.signin.viewmodel import androidx.lifecycle.ViewModel @@ -78,4 +79,4 @@ class NicknameViewModel @Inject constructor( fun onNavigated() { _uiState.update { it.copy(navigateToNext = false, errorMessage = null) } } -} \ No newline at end of file +}*/ 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 index 0f0b7ab5..7b7dfe8e 100644 --- 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 @@ -1,3 +1,4 @@ +/* package com.texthip.thip.ui.signin.viewmodel import androidx.lifecycle.ViewModel @@ -57,3 +58,4 @@ class SignupAliasViewModel @Inject constructor( } } +*/ diff --git a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt new file mode 100644 index 00000000..7e7fad4d --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt @@ -0,0 +1,114 @@ +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.manager.TokenManager +import com.texthip.thip.data.model.base.ThipApiFailureException +import com.texthip.thip.data.model.users.request.SignupRequest +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 + +data class SignupUiState( + val isLoading: Boolean = false, + val nickname: String = "", + val isNicknameVerified: Boolean = false, + val nicknameWarningMessageResId: Int? = null, + val roleCards: List = emptyList(), + val selectedIndex: Int = -1, + val errorMessage: String? = null, + val navigateToGenreScreen: Boolean = false, + val isSignupSuccess: Boolean = false +) + +@HiltViewModel +class SignupViewModel @Inject constructor( + private val userRepository: UserRepository, + private val tokenManager: TokenManager +) : ViewModel() { + + private val _uiState = MutableStateFlow(SignupUiState()) + val uiState = _uiState.asStateFlow() + + fun onNicknameChange(nickname: String) { + _uiState.update { + it.copy( + nickname = nickname, + isNicknameVerified = false, // 닉네임이 바뀌면 인증 상태 초기화 + nicknameWarningMessageResId = null, + navigateToGenreScreen = false + ) + } + } + + fun checkNickname() { + if (_uiState.value.isLoading || _uiState.value.nickname.isBlank()) return + + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true, nicknameWarningMessageResId = null) } + userRepository.checkNickname(_uiState.value.nickname) + .onSuccess { response -> + if (response?.isVerified == true) { + _uiState.update { it.copy(isLoading = false, isNicknameVerified = true, navigateToGenreScreen = true) } + } else { + _uiState.update { it.copy(isLoading = false, nicknameWarningMessageResId = R.string.nickname_warning) } + } + } + .onFailure { _uiState.update { it.copy(isLoading = false, nicknameWarningMessageResId = R.string.error_unknown) } } + } + } + + fun onNavigatedToGenre() { + _uiState.update { it.copy(navigateToGenreScreen = false) } + } + + fun fetchAliasChoices() { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + userRepository.getAliasChoices() + .onSuccess { response -> + val roleCards = response?.aliasChoices?.map { RoleItem(it.aliasName, it.categoryName, it.imageUrl, it.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) } + } + + fun signup() { + viewModelScope.launch { + val currentState = _uiState.value + val selectedRole = currentState.roleCards.getOrNull(currentState.selectedIndex) + + if (selectedRole == null || currentState.nickname.isBlank() || !currentState.isNicknameVerified) { + _uiState.update { it.copy(errorMessage = "닉네임과 역할을 모두 선택해주세요.") } + return@launch + } + + val request = SignupRequest( + nickName = currentState.nickname, + aliasName = selectedRole.genre + ) + + _uiState.update { it.copy(isLoading = true) } + userRepository.signup(request) + .onSuccess { authResponse -> + // TODO: 성공 시 받은 토큰(authResponse) 저장 및 메인 화면으로 이동 + _uiState.update { it.copy(isLoading = false, isSignupSuccess = true) } + } + .onFailure { exception -> + val errorMsg = if (exception is ThipApiFailureException) exception.message else "회원가입에 실패했습니다." + _uiState.update { it.copy(isLoading = false, errorMessage = errorMsg) } + } + } + } +} \ No newline at end of file From c7979357c71923b4a74bda2030bdfb31c0fcfc03 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 09:10:29 +0900 Subject: [PATCH 08/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B4=80=EB=A0=A8=20=EC=8A=A4=ED=81=AC=EB=A6=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../ui/signin/screen/SignupGenreScreen.kt | 29 +++++++++++++------ .../ui/signin/screen/SignupNicknameScreen.kt | 22 +++++++------- gradle/libs.versions.toml | 2 ++ 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 78ad2798..75d9c149 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { implementation(libs.kotlinx.serialization.json) implementation(libs.coil.compose) implementation(libs.foundation) + implementation(libs.androidx.datastore.preferences) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) 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 30f06578..b221b598 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 @@ -12,44 +12,55 @@ 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.LaunchedEffect import androidx.compose.runtime.getValue 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.signin.viewmodel.SignupUiState +import com.texthip.thip.ui.signin.viewmodel.SignupViewModel 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( - onNavigateToNext: (RoleItem) -> Unit, // 선택된 아이템 정보를 다음 화면으로 넘겨주기 - viewModel: SignupAliasViewModel = hiltViewModel() + viewModel: SignupViewModel, + onSignupSuccess: () -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + LaunchedEffect(Unit) { + viewModel.fetchAliasChoices() + } + LaunchedEffect(uiState.isSignupSuccess) { + if (uiState.isSignupSuccess) { + onSignupSuccess() + } + } + SignupGenreContent( uiState = uiState, - onCardSelected = { index -> viewModel.selectCard(index) }, + /*onCardSelected = { index -> viewModel.selectCard(index) }, onNextClick = { // 선택된 아이템이 있을 경우에만 다음 화면으로 이동 uiState.roleCards.getOrNull(uiState.selectedIndex)?.let { selectedRoleItem -> onNavigateToNext(selectedRoleItem) } - } + }*/ + onCardSelected = viewModel::selectCard, + onNextClick = viewModel::signup ) } @Composable fun SignupGenreContent( - uiState: SignupAliasUiState, + uiState: SignupUiState, onCardSelected: (Int) -> Unit, onNextClick: () -> Unit ) { @@ -125,7 +136,7 @@ private fun SignupGenreScreenPrev() { RoleItem("예술", "예술가", "", "#FFFFFF"), RoleItem("인문", "철학자", "", "#FFFFFF") ) - val previewUiState = SignupAliasUiState( + val previewUiState = SignupUiState( roleCards = previewRoleCards, selectedIndex = 1 // 1번 아이템이 선택된 상태로 프리뷰 ) 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 a844de6f..500ef73e 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 @@ -20,31 +20,31 @@ 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.signin.viewmodel.SignupViewModel import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun SignupNicknameScreen( - viewModel: NicknameViewModel = hiltViewModel(), - onNavigateToNext: () -> Unit + viewModel: SignupViewModel, + onNavigateToGenre: () -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current - - LaunchedEffect(uiState.navigateToNext, uiState.errorMessage) { - if (uiState.navigateToNext) { - onNavigateToNext() - viewModel.onNavigated() + LaunchedEffect(uiState.navigateToGenreScreen) { + if (uiState.navigateToGenreScreen) { + onNavigateToGenre() + viewModel.onNavigatedToGenre() } + } + + LaunchedEffect(uiState.errorMessage) { uiState.errorMessage?.let { message -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - viewModel.onNavigated() } } @@ -53,7 +53,7 @@ fun SignupNicknameScreen( onNicknameChange = viewModel::onNicknameChange, onNextClick = viewModel::checkNickname, isLoading = uiState.isLoading, - warningMessageResId = uiState.warningMessageResId + warningMessageResId = uiState.nicknameWarningMessageResId ) } @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 821d4cca..832f4cbf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ okhttp = "5.1.0" retrofit = "3.0.0" retrofitKotlinSerializationConverter = "1.0.0" androidxComposeNavigation = "2.8.2" +datastorePreferences = "1.1.7" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -58,6 +59,7 @@ logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", ver okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit-kotlin-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinSerializationConverter" } +androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } From d116345740d5df0978d9cbe21f18a58f728c790b Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 13:13:41 +0900 Subject: [PATCH 09/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B4=80=EB=A0=A8=20dto=20=EC=88=98=EC=A0=95=20(#7?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../texthip/thip/data/model/users/request/SignupRequest.kt | 2 +- .../texthip/thip/data/model/users/response/SignupResponse.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt b/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt index 32f4d9d3..934acab0 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt @@ -4,6 +4,6 @@ import kotlinx.serialization.Serializable @Serializable data class SignupRequest( - val nickName: String, + val nickname: String, val aliasName: String ) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt index 5f9167f1..9d0c63d6 100644 --- a/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt @@ -1,9 +1,10 @@ package com.texthip.thip.data.model.users.response +import com.google.gson.annotations.SerializedName import kotlinx.serialization.Serializable @Serializable data class SignupResponse( - val accessToken: String, - val refreshToken: String + @SerializedName("accessToken") val accessToken: String, + @SerializedName("userId") val userId: Long ) \ No newline at end of file From 767ea088ae89a0e5c15cfe62a45dfd9f9e774ad7 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 13:14:05 +0900 Subject: [PATCH 10/22] =?UTF-8?q?[feat]:=20mainactivity=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EB=A7=A4=EB=8B=88=EC=A0=80=20=EC=A3=BC=EC=9E=85=20(#7?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/texthip/thip/MainActivity.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/MainActivity.kt b/app/src/main/java/com/texthip/thip/MainActivity.kt index d527d5dd..f5da8ad4 100644 --- a/app/src/main/java/com/texthip/thip/MainActivity.kt +++ b/app/src/main/java/com/texthip/thip/MainActivity.kt @@ -9,13 +9,18 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import com.texthip.thip.data.manager.TokenManager import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + @Inject + lateinit var tokenManager: TokenManager + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() From 1f1e2c11c68da8d4ed430a810dbd459a460604f8 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 13:15:52 +0900 Subject: [PATCH 11/22] =?UTF-8?q?[feat]:=20signup=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../texthip/thip/data/repository/UserRepository.kt | 3 +++ .../thip/ui/mypage/screen/MypageEditScreen.kt | 2 -- .../thip/ui/signin/viewmodel/SignupViewModel.kt | 13 ++++++++++--- 3 files changed, 13 insertions(+), 5 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 bfece035..c3df5646 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 @@ -89,6 +89,9 @@ class UserRepository @Inject constructor( val tempToken = tokenManager.getTempToken() ?: return Result.failure(Exception("임시 토큰이 없습니다.")) + if (tempToken.isNullOrBlank()) { + return Result.failure(Exception("임시 토큰이 없습니다. 로그인을 다시 시도해주세요.")) + } return runCatching { userService.signup("Bearer $tempToken", request) .handleBaseResponse() 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 fcfae70b..6155b276 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 @@ -32,8 +32,6 @@ import com.texthip.thip.ui.mypage.component.RoleCard import com.texthip.thip.ui.mypage.mock.RoleItem import com.texthip.thip.ui.mypage.viewmodel.EditProfileUiState import com.texthip.thip.ui.mypage.viewmodel.EditProfileViewModel -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 diff --git a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt index 7e7fad4d..06c67444 100644 --- a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt @@ -3,7 +3,6 @@ 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.manager.TokenManager import com.texthip.thip.data.model.base.ThipApiFailureException import com.texthip.thip.data.model.users.request.SignupRequest import com.texthip.thip.data.repository.UserRepository @@ -30,7 +29,6 @@ data class SignupUiState( @HiltViewModel class SignupViewModel @Inject constructor( private val userRepository: UserRepository, - private val tokenManager: TokenManager ) : ViewModel() { private val _uiState = MutableStateFlow(SignupUiState()) @@ -95,7 +93,7 @@ class SignupViewModel @Inject constructor( } val request = SignupRequest( - nickName = currentState.nickname, + nickname = currentState.nickname, aliasName = selectedRole.genre ) @@ -111,4 +109,13 @@ class SignupViewModel @Inject constructor( } } } + // 소셜로그인과 연동 후 삭제 예정 + fun setInitialDataForTest(nickname: String) { + _uiState.update { + it.copy( + nickname = nickname, + isNicknameVerified = true // 닉네임이 검증되었다고 가정 + ) + } + } } \ No newline at end of file From 33cde2837d71b5ade49c2ddf0f37fc892cc9018f Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 14:47:43 +0900 Subject: [PATCH 12/22] =?UTF-8?q?[feat]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=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 --- .../navigator/navigations/signupNavigation.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt new file mode 100644 index 00000000..cdfaa516 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt @@ -0,0 +1,49 @@ +package com.texthip.thip.ui.navigator.navigations + +import androidx.compose.runtime.remember +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import com.texthip.thip.ui.navigator.routes.MainTabRoutes +import com.texthip.thip.ui.signin.screen.SignupGenreScreen +import com.texthip.thip.ui.signin.screen.SignupNicknameScreen +import com.texthip.thip.ui.signin.viewmodel.SignupViewModel + +fun NavGraphBuilder.signupNavigation(navController: NavHostController) { + navigation( + startDestination = "signup_nickname", + route = "signup_flow" + ) { + composable("signup_nickname") { navBackStackEntry -> + val parentEntry = remember(navBackStackEntry) { + navController.getBackStackEntry("signup_flow") + } + val viewModel: SignupViewModel = hiltViewModel(parentEntry) + + SignupNicknameScreen( + viewModel = viewModel, + onNavigateToGenre = { + navController.navigate("signup_genre") + } + ) + } + + composable("signup_genre"){ navBackStackEntry -> + val parentEntry = remember(navBackStackEntry) { + navController.getBackStackEntry("signup_flow") + } + val viewModel: SignupViewModel = hiltViewModel(parentEntry) + + SignupGenreScreen( + viewModel = viewModel, + onSignupSuccess = { + navController.navigate(MainTabRoutes.Feed) { + popUpTo("signup_flow") { inclusive = true } + } + } + ) + } + } +} \ No newline at end of file From 2f548b18b234d4ecdd0a2599ea76e82910b44e77 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 15:50:30 +0900 Subject: [PATCH 13/22] =?UTF-8?q?[feat]:=20=EB=8B=89=EB=84=A4=EC=9E=84,=20?= =?UTF-8?q?=EC=B9=AD=ED=98=B8=20=EC=84=A0=ED=83=9D=20=EB=B7=B0=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=82=AD=EC=A0=9C=20->=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/signin/viewmodel/NicknameViewModel.kt | 82 ------------------- .../signin/viewmodel/SignupAliasViewModel.kt | 61 -------------- 2 files changed, 143 deletions(-) delete mode 100644 app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt delete 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/NicknameViewModel.kt b/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt deleted file mode 100644 index 1d098c26..00000000 --- a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* -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) } - } -}*/ 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 deleted file mode 100644 index 7b7dfe8e..00000000 --- a/app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* -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 ad4bafc4207887b8fab437b7f06d4647b9a51225 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 15:50:46 +0900 Subject: [PATCH 14/22] =?UTF-8?q?[feat]:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EC=9D=91=EB=8B=B5=20dto=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/users/response/UserSearchResponse.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt new file mode 100644 index 00000000..9267f7d7 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt @@ -0,0 +1,18 @@ +package com.texthip.thip.data.model.users.response + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class UserSearchResponse( + val userList: List +) +@Serializable +data class UserItem( + @SerializedName("userId") val userId: Int, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String?, + @SerializedName("aliasName") val aliasName: String, + @SerializedName("aliasColor") val aliasColor: String, + @SerializedName("followerCount") val followerCount: Int +) \ No newline at end of file From ce0d2993e974413be967cc3726a940b2ff16997d Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 15:51:07 +0900 Subject: [PATCH 15/22] =?UTF-8?q?[feat]:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=83=9D?= =?UTF-8?q?=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 | 11 ++++++++++- .../java/com/texthip/thip/data/service/UserService.kt | 9 +++++++++ 2 files changed, 19 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 c3df5646..ed8abfad 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 @@ -14,6 +14,7 @@ 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.model.users.response.SignupResponse +import com.texthip.thip.data.model.users.response.UserSearchResponse import com.texthip.thip.data.service.UserService import javax.inject.Inject import javax.inject.Singleton @@ -87,7 +88,6 @@ class UserRepository @Inject constructor( suspend fun signup(request: SignupRequest): Result { val tempToken = tokenManager.getTempToken() - ?: return Result.failure(Exception("임시 토큰이 없습니다.")) if (tempToken.isNullOrBlank()) { return Result.failure(Exception("임시 토큰이 없습니다. 로그인을 다시 시도해주세요.")) @@ -98,4 +98,13 @@ class UserRepository @Inject constructor( .getOrThrow() } } + + suspend fun searchUsers( + keyword: String, + isFinalized: Boolean + ): Result = runCatching { + userService.searchUsers(isFinalized = isFinalized, keyword = keyword) + .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 90e3f178..6a85216e 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 @@ -13,6 +13,7 @@ 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.model.users.response.SignupResponse +import com.texthip.thip.data.model.users.response.UserSearchResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -65,4 +66,12 @@ interface UserService { @Header("Authorization") tempToken: String, @Body request: SignupRequest ): BaseResponse + + @GET("users") + suspend fun searchUsers( + @Query("isFinalized") isFinalized: Boolean, + @Query("keyword") keyword: String, + @Query("size") size: Int = 30 + ): BaseResponse + } \ No newline at end of file From 46a8b35a1ff3af0ab7bbb3c6b5dcc6d2c92fe13f Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 15:51:22 +0900 Subject: [PATCH 16/22] =?UTF-8?q?[feat]:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/viewmodel/SearchPeopleViewModel.kt | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt new file mode 100644 index 00000000..eac8a8ac --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt @@ -0,0 +1,99 @@ +package com.texthip.thip.ui.feed.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.repository.UserRepository +import com.texthip.thip.ui.feed.mock.MySubscriptionData +import com.texthip.thip.ui.feed.mock.toMySubscriptionData +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +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 SearchPeopleUiState( + val searchText: String = "", + val isSearched: Boolean = false, // 최종 검색 완료 여부 + val searchResults: List = emptyList(), + val recentSearches: List = listOf("메롱", "메메롱"), // TODO: 실제 최근 검색어 로딩 + val isLoading: Boolean = false, + val errorMessage: String? = null +) + +@HiltViewModel +class SearchPeopleViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(SearchPeopleUiState()) + val uiState = _uiState.asStateFlow() + + private var searchJob: Job? = null + + // 사용자가 텍스트를 입력할 때 호출 + fun onSearchTextChanged(text: String) { + _uiState.update { it.copy(searchText = text, isSearched = false) } + + // 이전 검색 요청이 있다면 취소 + searchJob?.cancel() + + if (text.isNotBlank()) { + searchJob = viewModelScope.launch { + delay(500L) + searchUsers(keyword = text, isFinalized = false) + } + } else { + // 입력창이 비워지면 검색 결과도 비움 + _uiState.update { it.copy(searchResults = emptyList()) } + } + } + + // 키보드의 '검색' 버튼이나 아이콘을 눌렀을 때 호출 + fun onFinalSearch(query: String) { + searchJob?.cancel() + _uiState.update { it.copy(isSearched = true) } + + if (query.isNotBlank()) { + addRecentSearch(query) + searchUsers(keyword = query, isFinalized = true) + } else { + _uiState.update { it.copy(searchResults = emptyList()) } + } + } + + // 실제 API를 호출하는 private 함수 + private fun searchUsers(keyword: String, isFinalized: Boolean) { + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + userRepository.searchUsers(keyword, isFinalized) + .onSuccess { response -> + val userList = + response?.userList?.map { it.toMySubscriptionData() } ?: emptyList() + _uiState.update { it.copy(isLoading = false, searchResults = userList) } + } + .onFailure { exception -> + _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } + } + } + } + + // 최근 검색어 관련 로직 + fun addRecentSearch(keyword: String) { + _uiState.update { currentState -> + val updatedSearches = (listOf(keyword) + currentState.recentSearches) + .distinct().take(10) + currentState.copy(recentSearches = updatedSearches) + } + } + + fun removeRecentSearch(keyword: String) { + _uiState.update { currentState -> + val updatedSearches = currentState.recentSearches.filterNot { it == keyword } + currentState.copy(recentSearches = updatedSearches) + } + } +} From a8efc5b94fe8ec1ef668d853aef65f84cb792cbc Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 15:51:42 +0900 Subject: [PATCH 17/22] =?UTF-8?q?[feat]:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=ED=99=94=EB=A9=B4=20screen,=20content=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/feed/screen/SearchPeopleScreen.kt | 296 ++++++++++-------- 1 file changed, 159 insertions(+), 137 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt index 17a011f3..8a6d09dd 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt @@ -1,7 +1,6 @@ package com.texthip.thip.ui.feed.screen import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -12,20 +11,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -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.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalFocusManager 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.SearchBookTextField import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar @@ -33,163 +27,191 @@ import com.texthip.thip.ui.feed.component.PeopleRecentSearch import com.texthip.thip.ui.feed.component.SearchPeopleEmptyResult import com.texthip.thip.ui.feed.component.SearchPeopleResult import com.texthip.thip.ui.feed.mock.MySubscriptionData +import com.texthip.thip.ui.feed.viewmodel.SearchPeopleUiState +import com.texthip.thip.ui.feed.viewmodel.SearchPeopleViewModel 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 SearchPeopleScreen( - modifier: Modifier = Modifier, - allPeople: List + viewModel: SearchPeopleViewModel = hiltViewModel() ) { - var recentSearches by rememberSaveable { - mutableStateOf(listOf("메롱", "메메롱", "메메메롱", "메메메", "메메루메루메루")) - } - var searchText by rememberSaveable { mutableStateOf("") } - var isSearched by rememberSaveable { mutableStateOf(false) } - val focusRequester = remember { FocusRequester() } + val uiState by viewModel.uiState.collectAsStateWithLifecycle() val focusManager = LocalFocusManager.current - val liveSearchResults by remember(searchText, allPeople) { - derivedStateOf { - if (searchText.isBlank()) emptyList() - else allPeople.filter { person -> - person.nickname.contains(searchText, ignoreCase = true) - } + LaunchedEffect(uiState.isSearched) { + if (uiState.isSearched) { + focusManager.clearFocus() } } - val finalSearchResults by remember(searchText, isSearched, allPeople) { - derivedStateOf { - if (isSearched) { - if (searchText.isNotBlank()) { - allPeople.filter { person -> - person.nickname.contains(searchText, ignoreCase = true) - } - } else { - emptyList() - } - } else { - emptyList() - } - } - } + SearchPeopleContent( + uiState = uiState, + onSearchTextChanged = viewModel::onSearchTextChanged, + onFinalSearch = viewModel::onFinalSearch, + onRecentSearchClick = { keyword -> viewModel.onFinalSearch(keyword) }, + onRecentSearchRemove = viewModel::removeRecentSearch + ) +} - LaunchedEffect(isSearched) { - if (isSearched) { - focusManager.clearFocus() - } - } +@Composable +fun SearchPeopleContent( + uiState: SearchPeopleUiState, + onSearchTextChanged: (String) -> Unit, + onFinalSearch: (String) -> Unit, + onRecentSearchClick: (String) -> Unit, + onRecentSearchRemove: (String) -> Unit + +) { - Box( - modifier = modifier.fillMaxSize() + Column( + modifier = Modifier.fillMaxSize() ) { - Column( - modifier = Modifier.fillMaxSize() - ) { - DefaultTopAppBar( - title = stringResource(R.string.search_user), - onLeftClick = {}, - ) - Column( - modifier = Modifier - .fillMaxSize() - ) { - Spacer(modifier = Modifier.height(16.dp)) - - SearchBookTextField( + DefaultTopAppBar( + title = stringResource(R.string.search_user), + onLeftClick = {}, + ) + Spacer(modifier = Modifier.height(16.dp)) + + SearchBookTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + hint = stringResource(R.string.search_user_you_look_for), + text = uiState.searchText, + onValueChange = onSearchTextChanged, + onSearch = onFinalSearch + ) + Spacer(modifier = Modifier.height(16.dp)) + + when { + uiState.isSearched && uiState.searchResults.isNotEmpty() -> { //검색했는데 결과 있음 + Row( modifier = Modifier .fillMaxWidth() - .focusRequester(focusRequester) .padding(horizontal = 20.dp), - hint = stringResource(R.string.search_user_you_look_for), - text = searchText, - onValueChange = { - searchText = it - isSearched = false - }, - onSearch = { query -> - if (query.isNotBlank() && !recentSearches.contains(query)) { - recentSearches = (listOf(query) + recentSearches).take(10) - } - isSearched = true - } - ) - Spacer(modifier = Modifier.height(16.dp)) - - when { - isSearched && finalSearchResults.isNotEmpty() -> { //검색했는데 결과 있음 - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.group_searched_room_size, finalSearchResults.size), - color = colors.Grey, - style = typography.menu_m500_s14_h24 - ) - } - Spacer( - modifier = Modifier - .padding(top = 4.dp, bottom = 16.dp) - .padding(horizontal = 20.dp) - .fillMaxWidth() - .height(1.dp) - .background(colors.DarkGrey02) - ) - SearchPeopleResult( - modifier = Modifier.weight(1f), - peopleList = finalSearchResults, - onThipNumClick = { person -> /*프로필로 이동*/ } - ) - } - isSearched && finalSearchResults.isEmpty() -> { //검색했는데 결과 없음 - SearchPeopleEmptyResult( - modifier = Modifier.padding(horizontal = 20.dp), - mainText = stringResource(R.string.no_user_you_look_for) - ) - } - searchText.isNotBlank() && !isSearched -> { //검색중 - SearchPeopleResult( - modifier = Modifier.weight(1f), - peopleList = liveSearchResults, - onThipNumClick = { person -> /* 프로필 화면으로 이동 */ } - ) - } - searchText.isBlank() && !isSearched -> { //최근검색어 보여주기 - PeopleRecentSearch( - modifier = Modifier.padding(horizontal = 20.dp), - recentSearches = recentSearches, - onSearchClick = { keyword -> - searchText = keyword - isSearched = true - }, - onRemove = { keyword -> - recentSearches = recentSearches.filterNot { it == keyword } - } - ) - } + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource( + R.string.group_searched_room_size, + uiState.searchResults.size + ), + color = colors.Grey, + style = typography.menu_m500_s14_h24 + ) } + Spacer( + modifier = Modifier + .padding(top = 4.dp, bottom = 16.dp) + .padding(horizontal = 20.dp) + .fillMaxWidth() + .height(1.dp) + .background(colors.DarkGrey02) + ) + SearchPeopleResult(peopleList = uiState.searchResults) + } + + uiState.isSearched && uiState.searchResults.isEmpty() -> { //검색했는데 결과 없음 + SearchPeopleEmptyResult( + modifier = Modifier.padding(horizontal = 20.dp), + mainText = stringResource(R.string.no_user_you_look_for) + ) + } + + uiState.searchText.isNotBlank() && !uiState.isSearched -> { //검색중 + SearchPeopleResult(peopleList = uiState.searchResults) + } + + else -> { //최근검색어 보여주기 + PeopleRecentSearch( + modifier = Modifier.padding(horizontal = 20.dp), + recentSearches = uiState.recentSearches, + onSearchClick = onRecentSearchClick, + onRemove = onRecentSearchRemove + ) } } + + + } +} + + +@Preview +@Composable +private fun SearchPeopleContentPreview_Recent() { + ThipTheme { + SearchPeopleContent( + uiState = SearchPeopleUiState( + recentSearches = listOf("메롱", "메메롱", "메메메롱") + ), + onSearchTextChanged = {}, + onFinalSearch = {}, + onRecentSearchClick = {}, + onRecentSearchRemove = {} + ) } } +@Preview +@Composable +private fun SearchPeopleContentPreview_Typing() { + val dummyResults = listOf( + MySubscriptionData(null, "메롱이", "인플루언서", colors.NeonGreen, 12, false), + MySubscriptionData(null, "메메롱이", "칭호", colors.NeonGreen, 1, false), + ) + ThipTheme { + SearchPeopleContent( + uiState = SearchPeopleUiState( + searchText = "메롱", + searchResults = dummyResults + ), + onSearchTextChanged = {}, + onFinalSearch = {}, + onRecentSearchClick = {}, + onRecentSearchRemove = {} + ) + } +} + +@Preview +@Composable +private fun SearchPeopleContentPreview_Result() { + val dummyResults = listOf( + MySubscriptionData(null, "Thip_Official", "인플루언서", colors.NeonGreen, 111, false), + MySubscriptionData(null, "thip01", "작가", colors.NeonGreen, 0, false) + ) + ThipTheme { + SearchPeopleContent( + uiState = SearchPeopleUiState( + searchText = "thip", + isSearched = true, + searchResults = dummyResults + ), + onSearchTextChanged = {}, + onFinalSearch = {}, + onRecentSearchClick = {}, + onRecentSearchRemove = {} + ) + } +} @Preview @Composable -fun PreviewGroupSearchScreen() { +private fun SearchPeopleContentPreview_Empty() { ThipTheme { - SearchPeopleScreen( - allPeople = listOf( - MySubscriptionData(null, "메롱이", "인플루언서", colors.NeonGreen, 12, false), - MySubscriptionData(null, "메메롱이", "칭호", colors.NeonGreen, 1, false), - MySubscriptionData(null, "thip", "칭호칭호", colors.NeonGreen, 11, false), - MySubscriptionData(null, "Thip", "인플루언서", colors.NeonGreen, 111, false), - MySubscriptionData(null, "thip01", "작가", colors.NeonGreen, 0, false) - ) + SearchPeopleContent( + uiState = SearchPeopleUiState( + searchText = "없는사용자", + isSearched = true, + searchResults = emptyList() + ), + onSearchTextChanged = {}, + onFinalSearch = {}, + onRecentSearchClick = {}, + onRecentSearchRemove = {} ) } } \ No newline at end of file From e3185803717deb87b2741640e7e30714ddee844f Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 17:20:38 +0900 Subject: [PATCH 18/22] =?UTF-8?q?[feat]:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=91=20=ED=86=A0=EC=8A=A4=ED=8A=B8,=20=EB=92=A4?= =?UTF-8?q?=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EA=B5=AC=ED=98=84=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/mypage/screen/MypageEditScreen.kt | 211 ++++++++++-------- .../thip/ui/mypage/screen/MypageScreen.kt | 5 +- .../mypage/viewmodel/MyPageEditViewModel.kt | 3 + .../ui/mypage/viewmodel/MyPageViewModel.kt | 3 - 4 files changed, 127 insertions(+), 95 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 6155b276..0d26e2bc 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 @@ -1,7 +1,13 @@ package com.texthip.thip.ui.mypage.screen +import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -13,20 +19,20 @@ 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.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment 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.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.texthip.thip.R -import com.texthip.thip.ui.common.forms.FormTextFieldDefault import com.texthip.thip.ui.common.forms.WarningTextField +import com.texthip.thip.ui.common.modal.ToastWithDate import com.texthip.thip.ui.common.topappbar.InputTopAppBar import com.texthip.thip.ui.mypage.component.RoleCard import com.texthip.thip.ui.mypage.mock.RoleItem @@ -35,18 +41,31 @@ import com.texthip.thip.ui.mypage.viewmodel.EditProfileViewModel import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import kotlinx.coroutines.delay @Composable fun EditProfileScreen( + onNavigateBack: () -> Unit, viewModel: EditProfileViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current + + LaunchedEffect(uiState.isSaveSuccess) { + if (uiState.isSaveSuccess) { + delay(2500L) + onNavigateBack() + viewModel.onSaveComplete() + } + } + EditProfileContent( uiState = uiState, onNicknameChange = viewModel::onNicknameChange, onCardSelected = viewModel::selectCard, - onSaveClick = viewModel::saveProfile + onSaveClick = viewModel::saveProfile, + onNavigateBack = onNavigateBack ) } @@ -56,105 +75,114 @@ fun EditProfileContent( uiState: EditProfileUiState, onNicknameChange: (String) -> Unit, onCardSelected: (Int) -> Unit, - onSaveClick: () -> Unit + onSaveClick: () -> Unit, + onNavigateBack: () -> Unit ) { - val isChanged = uiState.nickname != uiState.initialNickname || uiState.selectedIndex != uiState.initialSelectedIndex + val isChanged = + uiState.nickname != uiState.initialNickname || uiState.selectedIndex != uiState.initialSelectedIndex val isRightButtonEnabled = isChanged && uiState.nickname.isNotBlank() && !uiState.isLoading - Column( + Box( Modifier .fillMaxSize() ) { - InputTopAppBar( - title = stringResource(R.string.edit_profile), - isRightButtonEnabled = isRightButtonEnabled, - onLeftClick = {}, - onRightClick = onSaveClick - ) Column( - modifier = Modifier - .padding(horizontal = 20.dp) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + Modifier + .fillMaxSize() ) { - Spacer(modifier = Modifier.height(40.dp)) - Text( - text = stringResource(R.string.change_nickname), - style = typography.smalltitle_sb600_s18_h24, - color = colors.White, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 12.dp) - ) - //TODO 컴포넌트 수정 필요 -> text count 추가, boolean 값으로 icon, limit 설정가능하도록 - /* FormTextFieldDefault( - value = uiState.nickname, - onValueChange = onNicknameChange, - modifier = Modifier.fillMaxWidth(), - showLimit = true, - limit = 10, - showIcon = false, - containerColor = colors.DarkGrey02, - hint = stringResource(R.string.change_nickname) - )*/ - WarningTextField( - containerColor = colors.DarkGrey02, - value = uiState.nickname, - onValueChange = onNicknameChange, - hint = stringResource(R.string.nickname_condition), - showWarning = uiState.nicknameWarningMessageResId != null, - showIcon = false, - showLimit = true, - maxLength = 10, - warningMessage = uiState.nicknameWarningMessageResId?.let { stringResource(it) } ?: "" - ) - Spacer(modifier = Modifier.height(40.dp)) - Text( - text = stringResource(R.string.edit_role), - style = typography.smalltitle_sb600_s18_h24, - color = colors.White, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 8.dp) - ) - Text( - text = stringResource(R.string.role_description), - style = typography.copy_r400_s14, - color = colors.White, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 20.dp) + InputTopAppBar( + title = stringResource(R.string.edit_profile), + isRightButtonEnabled = isRightButtonEnabled, + onLeftClick = onNavigateBack, + onRightClick = onSaveClick ) - /*Text( - text = stringResource(R.string.choice_one), - style = typography.info_r400_s12, - color = colors.NeonGreen, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 12.dp) - )*/ - - - LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = 152.dp), // 카드 최소 크기 + Column( modifier = Modifier + .padding(horizontal = 20.dp) .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - userScrollEnabled = false, + horizontalAlignment = Alignment.CenterHorizontally ) { - itemsIndexed(uiState.roleCards) { index, roleItem -> - RoleCard( - genre = roleItem.genre, - role = roleItem.role, - imageUrl = roleItem.imageUrl, - roleColor = roleItem.roleColor, - selected = uiState.selectedIndex == index, - onClick = { onCardSelected(index) } - ) + Spacer(modifier = Modifier.height(40.dp)) + Text( + text = stringResource(R.string.change_nickname), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) + ) + WarningTextField( + containerColor = colors.DarkGrey02, + value = uiState.nickname, + onValueChange = onNicknameChange, + //hint = stringResource(R.string.nickname_condition), + hint = uiState.nickname, + showWarning = uiState.nicknameWarningMessageResId != null, + showIcon = false, + showLimit = true, + maxLength = 10, + warningMessage = uiState.nicknameWarningMessageResId?.let { stringResource(it) } + ?: "" + ) + Spacer(modifier = Modifier.height(40.dp)) + Text( + text = stringResource(R.string.edit_role), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) + ) + Text( + text = stringResource(R.string.role_description), + style = typography.copy_r400_s14, + color = colors.White, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 20.dp) + ) + + + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 152.dp), // 카드 최소 크기 + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + userScrollEnabled = false, + ) { + itemsIndexed(uiState.roleCards) { index, roleItem -> + RoleCard( + genre = roleItem.genre, + role = roleItem.role, + imageUrl = roleItem.imageUrl, + roleColor = roleItem.roleColor, + selected = uiState.selectedIndex == index, + onClick = { onCardSelected(index) } + ) + } } - } + } + } + AnimatedVisibility( + visible = uiState.isSaveSuccess, + enter = slideInVertically( + initialOffsetY = { -it }, + animationSpec = tween(durationMillis = 2000) + ), + exit = slideOutVertically( + targetOffsetY = { -it }, + animationSpec = tween(durationMillis = 2000) + ), + modifier = Modifier + .align(Alignment.TopCenter) + .padding(horizontal = 20.dp, vertical = 16.dp) + .zIndex(3f) + ) { + ToastWithDate( + message = stringResource(R.string.profile_edit_completely) + ) } } } @@ -178,7 +206,8 @@ private fun EditProfileScreenPrev() { uiState = previewUiState, onNicknameChange = {}, onCardSelected = {}, - onSaveClick = {} + onSaveClick = {}, + onNavigateBack = {} ) } } \ No newline at end of file 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 b91bef0f..6296ea78 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 @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -46,7 +47,9 @@ fun MyPageScreen( onDeleteAccount: () -> Unit ) { val uiState by viewModel.uiState.collectAsState() - + LaunchedEffect(Unit) { + viewModel.fetchMyPageInfo() + } MyPageContent( uiState = uiState, onEditProfileClick = onNavigateToEditProfile, diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt index 99dbc98a..7cb0777e 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt @@ -139,4 +139,7 @@ class EditProfileViewModel @Inject constructor( } } } + fun onSaveComplete() { + _uiState.update { it.copy(isSaveSuccess = false) } + } } \ No newline at end of file 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 index bf569aec..40e91a8d 100644 --- 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 @@ -27,9 +27,6 @@ class MyPageViewModel @Inject constructor( private val _uiState = MutableStateFlow(MyPageUiState()) val uiState = _uiState.asStateFlow() - init { - fetchMyPageInfo() - } fun fetchMyPageInfo() { viewModelScope.launch { From 208818c7b8c4b3d71388df4e6c27a7827c58e0b1 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 17:54:49 +0900 Subject: [PATCH 19/22] =?UTF-8?q?[feat]:=20=EA=B3=A0=EA=B0=9D=EC=84=BC?= =?UTF-8?q?=ED=84=B0=20screen=20=EC=B6=94=EA=B0=80,=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=97=B0=EA=B2=B0=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/MypageCustomerServiceScreen.kt | 71 +++++++++++++++++++ .../thip/ui/mypage/screen/MypageEditScreen.kt | 2 +- .../ui/mypage/screen/MypageLeavethipScreen.kt | 10 ++- .../screen/MypageNotificationEditScreen.kt | 10 ++- .../thip/ui/mypage/screen/MypageSaveScreen.kt | 5 +- .../thip/ui/mypage/screen/MypageScreen.kt | 6 +- .../extensions/MyPageNavigationExtensions.kt | 4 ++ .../navigator/navigations/MyPageNavigation.kt | 24 +++++-- .../thip/ui/navigator/routes/MyPageRoutes.kt | 1 + app/src/main/res/values/strings.xml | 6 +- 10 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt new file mode 100644 index 00000000..e01ada44 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt @@ -0,0 +1,71 @@ +package com.texthip.thip.ui.mypage.screen + +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.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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 com.texthip.thip.R +import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar +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 MypageCustomerServiceScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {} +) { + Column( + modifier = modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + DefaultTopAppBar( + title = stringResource(R.string.customer_service), + onLeftClick = onNavigateBack, + ) + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.customer_center_email), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White + ) + Spacer(modifier = Modifier.padding(top = 8.dp)) + + Text( + text = stringResource(R.string.customer_center_description), + style = typography.copy_r400_s14, + color = colors.White + ) + Text( + text = stringResource(R.string.customer_center_description_2), + style = typography.copy_r400_s14, + color = colors.White + ) + } + } +} + +@Preview +@Composable +private fun MypageCustomerServiceScreenPrev() { + ThipTheme { + MypageCustomerServiceScreen( + onNavigateBack = {} + ) + } +} 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 0d26e2bc..140c7ba2 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 @@ -116,7 +116,7 @@ fun EditProfileContent( value = uiState.nickname, onValueChange = onNicknameChange, //hint = stringResource(R.string.nickname_condition), - hint = uiState.nickname, + hint = uiState.initialNickname.takeIf { it.isNotBlank() } ?: stringResource(R.string.nickname_condition), showWarning = uiState.nicknameWarningMessageResId != null, showIcon = false, showLimit = true, diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt index 31b6f7bb..7b6d8172 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt @@ -39,7 +39,9 @@ import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable -fun DeleteAccountScreen() { +fun DeleteAccountScreen( + onNavigateBack: () -> Unit +) { var isChecked by rememberSaveable { mutableStateOf(false) } val backgroundColor = if (isChecked) colors.Purple else colors.Grey02 var isDialogVisible by rememberSaveable { mutableStateOf(false) } @@ -51,7 +53,7 @@ fun DeleteAccountScreen() { ) { DefaultTopAppBar( title = stringResource(R.string.delete_account), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Spacer(modifier = Modifier.height(40.dp)) Column( @@ -169,5 +171,7 @@ fun DeleteAccountScreen() { @Preview @Composable private fun DeleteAccountScreenPrev() { - DeleteAccountScreen() + DeleteAccountScreen( + onNavigateBack = {} + ) } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt index af5b4fe8..137f8cb6 100644 --- a/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt @@ -32,7 +32,9 @@ import com.texthip.thip.ui.theme.ThipTheme.typography import kotlinx.coroutines.delay @Composable -fun NotificationScreen() { +fun NotificationScreen( + onNavigateBack: () -> Unit +) { var isChecked by rememberSaveable { mutableStateOf(true) } var toastMessage by rememberSaveable { mutableStateOf(null) } @@ -68,7 +70,7 @@ fun NotificationScreen() { ) { DefaultTopAppBar( title = stringResource(R.string.notification_settings), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Spacer(modifier = Modifier.height(40.dp)) Column( @@ -114,5 +116,7 @@ fun NotificationScreen() { @Preview @Composable private fun NotificationScreenPrev() { - NotificationScreen() + NotificationScreen( + onNavigateBack = {} + ) } \ No newline at end of file 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 8885b79d..e3b64629 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 @@ -45,6 +45,7 @@ import com.texthip.thip.ui.theme.White @Composable fun SavedScreen( + onNavigateBack: () -> Unit, feedViewModel: SavedFeedViewModel = viewModel(), bookViewModel: SavedBookViewModel = viewModel() @@ -61,7 +62,7 @@ fun SavedScreen( ) { DefaultTopAppBar( title = stringResource(R.string.saved), - onLeftClick = {}, + onLeftClick = onNavigateBack, ) Column( modifier = Modifier @@ -124,6 +125,7 @@ fun SavedScreen( @Composable private fun SavedScreenPrev() { SavedScreen( + onNavigateBack = {}, feedViewModel = SavedFeedViewModel(), bookViewModel = SavedBookViewModel() ) @@ -135,6 +137,7 @@ private fun SavedScreenPrev() { private fun SavedScreenWithoutFeedPrev() { ThipTheme { SavedScreen( + onNavigateBack = {}, feedViewModel = EmptySavedFeedViewModel(), bookViewModel = EmptySavedBookViewModel() ) 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 6296ea78..a4799cea 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 @@ -43,6 +43,7 @@ fun MyPageScreen( viewModel: MyPageViewModel = hiltViewModel(), onNavigateToEditProfile: () -> Unit, onNavigateToSavedFeeds: () -> Unit, + onCustomerService: () -> Unit, onNavigateToNotificationSettings: () -> Unit, onDeleteAccount: () -> Unit ) { @@ -55,6 +56,7 @@ fun MyPageScreen( onEditProfileClick = onNavigateToEditProfile, onSavedFeedsClick = onNavigateToSavedFeeds, onNotificationSettingsClick = onNavigateToNotificationSettings, + onCustomerServiceClick = onCustomerService, onLogoutClick = { viewModel.onLogoutClick() }, onDismissLogoutDialog = { viewModel.onDismissLogoutDialog() }, onConfirmLogout = { viewModel.confirmLogout() }, @@ -67,6 +69,7 @@ fun MyPageContent( onEditProfileClick: () -> Unit, onSavedFeedsClick: () -> Unit, onNotificationSettingsClick: () -> Unit, + onCustomerServiceClick: () -> Unit, onLogoutClick: () -> Unit, onDismissLogoutDialog: () -> Unit, onConfirmLogout: () -> Unit, @@ -160,7 +163,7 @@ fun MyPageContent( backgroundColor = colors.DarkGrey02, hasRightIcon = true, modifier = Modifier.fillMaxWidth(), - onClick = {} + onClick = onCustomerServiceClick ) Spacer(modifier = Modifier.height(16.dp)) MenuItemButton( @@ -249,6 +252,7 @@ private fun MyPagePrev() { onEditProfileClick = {}, onSavedFeedsClick = {}, onNotificationSettingsClick = {}, + onCustomerServiceClick = {}, onDismissLogoutDialog = {}, onConfirmLogout = {}, onDeleteAccount = {} 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 153d958e..e02c3928 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 @@ -24,4 +24,8 @@ fun NavHostController.navigateToNotificationSettings() { fun NavHostController.navigateToLeaveThipScreen() { navigate(MyPageRoutes.LeaveThip) +} + +fun NavHostController.navigateToCustomerService() { + navigate(MyPageRoutes.CustomerService) } \ No newline at end of file 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 f318dfb6..a43b7a4d 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 @@ -7,8 +7,10 @@ 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.MypageCustomerServiceScreen import com.texthip.thip.ui.mypage.screen.NotificationScreen import com.texthip.thip.ui.mypage.screen.SavedScreen +import com.texthip.thip.ui.navigator.extensions.navigateToCustomerService import com.texthip.thip.ui.navigator.extensions.navigateToEditProfile import com.texthip.thip.ui.navigator.extensions.navigateToLeaveThipScreen import com.texthip.thip.ui.navigator.extensions.navigateToNotificationSettings @@ -24,20 +26,34 @@ fun NavGraphBuilder.myPageNavigation(navController: NavHostController) { onNavigateToEditProfile = { navController.navigateToEditProfile() }, onNavigateToSavedFeeds = { navController.navigateToSavedFeeds() }, onNavigateToNotificationSettings = { navController.navigateToNotificationSettings() }, + onCustomerService = {navController.navigateToCustomerService()}, onDeleteAccount = { navController.navigateToLeaveThipScreen() } ) } composable { - EditProfileScreen() + EditProfileScreen( + onNavigateBack = { navController.popBackStack() } + ) } composable { - SavedScreen() + SavedScreen( + onNavigateBack = { navController.popBackStack() } + ) } composable { - NotificationScreen() + NotificationScreen( + onNavigateBack = { navController.popBackStack() } + ) } composable { - DeleteAccountScreen() + DeleteAccountScreen( + onNavigateBack = { navController.popBackStack() } + ) + } + composable { + MypageCustomerServiceScreen ( + onNavigateBack = { navController.popBackStack() } + ) } } \ 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 3c0beed9..37959f5c 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 @@ -9,4 +9,5 @@ sealed class MyPageRoutes : Routes() { @Serializable data object Reaction : MyPageRoutes() @Serializable data object NotificationEdit : MyPageRoutes() @Serializable data object LeaveThip : MyPageRoutes() + @Serializable data object CustomerService : MyPageRoutes() } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39d84e5c..83e1e6c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -123,7 +123,8 @@ 푸시 알림이 설정되었어요. texthip2025@gmail.com - 이메일로 닉네임과 문의사항을 보내주시면\n빠른 시일 내로 해결해 드릴게요! + 이메일로 닉네임과 문의사항을 보내주시면 + 빠른 시일 내로 해결해 드릴게요! 아직 저장한 책이 없어요 아직 저장한 피드가 없어요 @@ -138,6 +139,9 @@ 현재 닉네임과 같아요 알 수 없는 오류가 발생했어요 + 프로필 설정을 성공적으로 변경했어요. + + 진행중 모집중 From f961f965067ab5421e20334ddeffb8d7a6a706ae Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 17:56:56 +0900 Subject: [PATCH 20/22] =?UTF-8?q?[feat]:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../texthip/thip/ui/feed/mock/MySubscriptionData.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt b/app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt index 0b222adb..51c565b9 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt @@ -2,6 +2,8 @@ package com.texthip.thip.ui.feed.mock import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import com.texthip.thip.data.model.users.response.UserItem +import com.texthip.thip.utils.color.hexToColor data class MySubscriptionData( val profileImageUrl: String? = null, @@ -11,3 +13,14 @@ data class MySubscriptionData( val subscriberCount: Int = 0, var isSubscribed: Boolean = true ) + +fun UserItem.toMySubscriptionData(): MySubscriptionData { + return MySubscriptionData( + profileImageUrl = this.profileImageUrl, + nickname = this.nickname, + role = this.aliasName, + roleColor = hexToColor(this.aliasColor), + subscriberCount = this.followerCount, + isSubscribed = false // API 응답에 구독 여부 정보가 없음 + ) +} From 7a4d646df7aa5d952976d1411e19b8b05f635ab1 Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 18:07:16 +0900 Subject: [PATCH 21/22] =?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=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20dto=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/response/OthersFollowersResponse.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt new file mode 100644 index 00000000..c465db0f --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt @@ -0,0 +1,22 @@ +package com.texthip.thip.data.model.users.response + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class OthersFollowersResponse( + @SerializedName("followers") val followers: List, + @SerializedName("totalFollowerCount") val totalFollowerCount: Int, + @SerializedName("nextCursor") val nextCursor: String?, + @SerializedName("isLast") val isLast: Boolean +) + +@Serializable +data class FollowerList( + @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("followerCount") val followerCount: Int +) \ No newline at end of file From a2884ff45dd6d5d151a97a9e6b9793b3be2f975f Mon Sep 17 00:00:00 2001 From: JJUYAAA Date: Mon, 18 Aug 2025 19:01:56 +0900 Subject: [PATCH 22/22] =?UTF-8?q?[feat]:=20MyRecentFollowingsResponse=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EA=B0=92=20=EC=88=98=EC=A0=95=20+=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EC=8A=A4=ED=81=AC=EB=A6=B0=20init=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/model/users/response/MyFollowingsResponse.kt | 2 +- .../java/com/texthip/thip/ui/feed/screen/FeedScreen.kt | 6 +++++- .../texthip/thip/ui/feed/viewmodel/FeedViewModel.kt | 10 +++------- 3 files changed, 9 insertions(+), 9 deletions(-) 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 21b395df..eb9a3de8 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 @@ -23,7 +23,7 @@ data class FollowingList( @Serializable data class MyRecentFollowingsResponse( - @SerializedName("recentWriters") val recentWriters: List + @SerializedName("myFollowingUsers") val myFollowingUsers: List ) @Serializable 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 218c0a4c..b2259394 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 @@ -13,6 +13,7 @@ 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.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -56,6 +57,9 @@ fun FeedScreen( followerProfileImageUrls: List = emptyList(), viewModel: FeedViewModel = hiltViewModel() ) { + LaunchedEffect(Unit) { + viewModel.fetchRecentWriters() + } val feedUiState by viewModel.uiState.collectAsState() val selectedIndex = rememberSaveable { mutableIntStateOf(selectedTabIndex) } val feedStateList = remember { @@ -213,7 +217,7 @@ fun FeedScreen( //피드 item { Spacer(modifier = Modifier.height(20.dp)) - val subscriptionsForBar = feedUiState.recentWriters.map { user -> + val subscriptionsForBar = feedUiState.myFollowingUsers.map { user -> MySubscriptionData( profileImageUrl = user.profileImageUrl, nickname = user.nickname, 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 index 04b3bdd5..91e8058a 100644 --- 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 @@ -13,7 +13,7 @@ import javax.inject.Inject data class FeedUiState( val isLoading: Boolean = true, - val recentWriters: List = emptyList(), + val myFollowingUsers: List = emptyList(), val errorMessage: String? = null //TODO 추후 피드 목록 등 다른 상태들 추가될 예정 ) @@ -26,11 +26,7 @@ class FeedViewModel @Inject constructor( private val _uiState = MutableStateFlow(FeedUiState()) val uiState = _uiState.asStateFlow() - init { - fetchRecentWriters() - } - - private fun fetchRecentWriters() { + fun fetchRecentWriters() { viewModelScope.launch { _uiState.update { it.copy(isLoading = true) } userRepository.getRecentWriters() @@ -38,7 +34,7 @@ class FeedViewModel @Inject constructor( _uiState.update { it.copy( isLoading = false, - recentWriters = data?.recentWriters ?: emptyList() + myFollowingUsers = data?.myFollowingUsers ?: emptyList() ) } }