From d1e65496248c78af49a26e468ba1d773441b5ece Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Mon, 2 Dec 2024 23:00:02 +0900 Subject: [PATCH] =?UTF-8?q?[feat/#10]=20=EC=84=9C=EB=B2=84=ED=86=B5?= =?UTF-8?q?=EC=8B=A0=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EA=B3=A0,=20=EA=B2=80=EC=83=89=ED=95=98=EA=B8=B0?= =?UTF-8?q?=EC=99=80=20=ED=9A=8C=EC=9B=90=EC=A0=95=EB=B3=B4=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/auth/screen/AuthViewModel.kt | 22 ++- .../ui/auth/screen/SignInScreen.kt | 14 +- .../ui/auth/screen/SignUpScreen.kt | 12 +- .../ui/main/navigation/MainNavGraph.kt | 22 +-- .../ui/main/navigation/MainNavigator.kt | 4 + .../presentation/ui/main/screen/HomeScreen.kt | 8 + .../presentation/ui/main/screen/MainScreen.kt | 35 +---- .../ui/main/screen/MainViewModel.kt | 84 ++++++---- .../ui/main/screen/MyPageScreen.kt | 40 ++++- .../ui/main/screen/SearchScreen.kt | 130 +++++++++++++++- .../ui/main/screen/SettingScreen.kt | 145 ++++++++++++++++++ .../ui/main/screen/UserHobbyState.kt | 5 +- .../ui/main/screen/UserInfoUpdateState.kt | 10 ++ 13 files changed, 448 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/presentation/ui/main/screen/SettingScreen.kt create mode 100644 app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserInfoUpdateState.kt diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt index 14bd85e..773afbd 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt @@ -33,7 +33,7 @@ class AuthViewModel @Inject constructor( _signInState.value = result.fold( onSuccess = { token -> tokenRepository.setToken(token.token) - Log.d("token","토큰 저장 완료! 저장값은 ${token.token} 입니다.") + Log.d("token", "토큰 저장 완료! 저장값은 ${token.token} 입니다.") SignInState.Success(token) }, onFailure = { @@ -46,11 +46,13 @@ class AuthViewModel @Inject constructor( fun validateSignUp(username: String, password: String, hobby: String) { _signUpState.value = SignUpState.Loading viewModelScope.launch { - val result = authRepository.registerUser(user = User( - username = username, - password = password, - hobby = hobby - )) + val result = authRepository.registerUser( + user = User( + username = username, + password = password, + hobby = hobby + ) + ) _signUpState.value = result.fold( onSuccess = { SignUpState.Success(it) @@ -61,4 +63,12 @@ class AuthViewModel @Inject constructor( ) } } + + fun resetSignInState() { + _signInState.value = SignInState.Idle + } + + fun resetSignUpState() { + _signUpState.value = SignUpState.Idle + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt index cfbdd02..a35ba2b 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt @@ -37,7 +37,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.and.R -import org.sopt.and.presentation.ui.auth.component.AuthTextField +import org.sopt.and.presentation.ui.common.WavveTextField import org.sopt.and.presentation.ui.auth.component.SocialPlatformIconRow import org.sopt.and.presentation.ui.auth.component.SocialPlatformList import org.sopt.and.presentation.util.showToast @@ -53,6 +53,7 @@ fun SignInRoute( SignInScreen( signInState = signInState, + resetSignInState = { authViewModel.resetSignInState() }, onSignUpClick = navigateToSignUp, onSignInClick = { email, password -> authViewModel.validateSignIn(email, password) }, navigateToMain = navigateToMain @@ -62,6 +63,7 @@ fun SignInRoute( @Composable fun SignInScreen( signInState: SignInState, + resetSignInState: () -> Unit, onSignUpClick: () -> Unit, onSignInClick: (String, String) -> Unit, navigateToMain: () -> Unit, @@ -102,7 +104,7 @@ fun SignInScreen( Spacer(Modifier.height(40.dp)) - AuthTextField( + WavveTextField( value = inputEmail, onValueChange = { newValue -> inputEmail = newValue }, modifier = Modifier @@ -112,7 +114,7 @@ fun SignInScreen( hint = "이메일 주소 또는 아이디" ) Spacer(Modifier.height(4.dp)) - AuthTextField( + WavveTextField( value = inputPassword, onValueChange = { newValue -> inputPassword = newValue }, modifier = Modifier @@ -215,20 +217,24 @@ fun SignInScreen( Spacer(modifier = Modifier.weight(1f)) - when(signInState) { + when (signInState) { is SignInState.Success -> { showToast( context = context, message = "로그인에 성공했습니다." ) + resetSignInState() navigateToMain() } + is SignInState.Failure -> { showToast( context = context, message = "아이디와 비밃번호를 다시 확인해주세요." ) + resetSignInState() } + else -> {} } } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt index 5146693..cebe5b7 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.and.R -import org.sopt.and.presentation.ui.auth.component.AuthTextField +import org.sopt.and.presentation.ui.common.WavveTextField import org.sopt.and.presentation.ui.auth.component.SocialPlatformIconRow import org.sopt.and.presentation.ui.auth.component.SocialPlatformList import org.sopt.and.presentation.util.showToast @@ -51,6 +51,7 @@ fun SignUpRoute( SignUpScreen( signUpState = signUpState, + resetSignUpState = { authViewModel.resetSignUpState() }, onSignUpClick = { username, password, hobby -> authViewModel.validateSignUp(username, password, hobby)}, navigateToSignIn = navigateToSignIn, onCancelClick = navigateToBack, @@ -60,6 +61,7 @@ fun SignUpRoute( @Composable fun SignUpScreen( signUpState: SignUpState, + resetSignUpState: () -> Unit, onSignUpClick: (String, String, String) -> Unit, navigateToSignIn: () -> Unit, onCancelClick: () -> Unit, @@ -131,7 +133,7 @@ fun SignUpScreen( Spacer(modifier = Modifier.height(18.dp)) - AuthTextField( + WavveTextField( value = inputEmail, onValueChange = { newValue -> inputEmail = newValue }, modifier = Modifier @@ -163,7 +165,7 @@ fun SignUpScreen( Spacer(modifier = Modifier.height(10.dp)) - AuthTextField( + WavveTextField( value = inputPassword, onValueChange = { newValue -> inputPassword = newValue }, modifier = Modifier @@ -194,7 +196,7 @@ fun SignUpScreen( ) } - AuthTextField( + WavveTextField( value = inputHobby, onValueChange = { newValue -> inputHobby = newValue }, modifier = Modifier @@ -309,12 +311,14 @@ fun SignUpScreen( message = "회원가입에 성공했습니다. 유저번호는 ${signUpState.result.no}입니다." ) navigateToSignIn() + resetSignUpState() } is SignUpState.Failure -> { showToast( context = context, message = "회원가입에 실패했습니다. 형식을 다시 확인해주세요." ) + resetSignUpState() } else -> {} } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt index cca39d2..febcc6c 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt @@ -4,10 +4,11 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.navigation -import org.sopt.and.presentation.ui.main.screen.HomeScreen +import org.sopt.and.presentation.ui.main.screen.HomeRoute import org.sopt.and.presentation.ui.main.screen.MainRoute -import org.sopt.and.presentation.ui.main.screen.MyPageScreen -import org.sopt.and.presentation.ui.main.screen.SearchScreen +import org.sopt.and.presentation.ui.main.screen.MyPageRoute +import org.sopt.and.presentation.ui.main.screen.SearchRoute +import org.sopt.and.presentation.ui.main.screen.SettingRoute import org.sopt.and.presentation.ui.navigation.WavveRoute fun NavGraphBuilder.mainNavGraph( @@ -19,19 +20,22 @@ fun NavGraphBuilder.mainNavGraph( ) { composable(route = WavveRoute.MAIN) { MainRoute( - navigateToHome = { navController.navigateToHome() }, - navigateToSearch = { navController.navigateToSearch() }, - navigateToMy = { navController.navigateToMy() } + navigateToSetting = { navController.navigateToSetting() } ) } composable(route = WavveRoute.HOME) { - HomeScreen() + HomeRoute() } composable(route = WavveRoute.SEARCH) { - SearchScreen() + SearchRoute() } composable(route = WavveRoute.MY) { - MyPageScreen(userHobby = "") + MyPageRoute( + navigateToSetting = { navController.navigateToSetting() } + ) + } + composable(route = WavveRoute.SETTING) { + SettingRoute() } } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt index f4b2028..4336c03 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt @@ -17,4 +17,8 @@ fun NavController.navigateToSearch() { fun NavController.navigateToMy() { navigate(WavveRoute.MY) +} + +fun NavController.navigateToSetting() { + navigate(WavveRoute.SETTING) } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt index 2d8b56e..265bf72 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.delay import org.sopt.and.R import org.sopt.and.presentation.ui.main.component.CategoryItem @@ -41,6 +42,13 @@ import org.sopt.and.presentation.ui.main.component.editorRecommendationImages import org.sopt.and.presentation.ui.main.component.todayTop20Images import org.sopt.and.ui.theme.ANDANDROIDTheme +@Composable +fun HomeRoute( + mainViewModel: MainViewModel = hiltViewModel() +) { + HomeScreen() +} + @Composable fun HomeScreen() { LazyColumn( diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt index 7bd8cfd..dd74556 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt @@ -35,24 +35,16 @@ import org.sopt.and.ui.theme.ANDANDROIDTheme @Composable fun MainRoute( mainViewModel: MainViewModel = hiltViewModel(), - navigateToHome: () -> Unit, - navigateToSearch: () -> Unit, - navigateToMy: () -> Unit, + navigateToSetting: () -> Unit ) { - val userHobbyState by mainViewModel.userHobbyState.collectAsState() - - LaunchedEffect(Unit) { - mainViewModel.getUserHobby() - } - MainScreen( - userHobbyState = userHobbyState, + navigateToSetting = navigateToSetting ) } @Composable fun MainScreen( - userHobbyState: UserHobbyState, + navigateToSetting: ()-> Unit ) { var selectedTab by remember { mutableStateOf(MainTabList.HOME) } val onTabSelected: (MainTabList) -> Unit = { tab -> @@ -144,22 +136,11 @@ fun MainScreen( .padding(innerPadding) ) { when (selectedTab) { - MainTabList.HOME -> HomeScreen() - MainTabList.SEARCH -> SearchScreen() - MainTabList.MY -> { - when (userHobbyState) { - is UserHobbyState.Success -> { - val userHobby = userHobbyState.hobby - MyPageScreen(userHobby = userHobby) - } - - is UserHobbyState.Failure -> { - val errorMessage = userHobbyState.errorMessage - } - - else -> {} - } - } + MainTabList.HOME -> HomeRoute() + MainTabList.SEARCH -> SearchRoute() + MainTabList.MY -> MyPageRoute( + navigateToSetting = navigateToSetting + ) } } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt index 60d24e2..77c6148 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt @@ -6,47 +6,79 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import org.sopt.and.data.local.datasource.TokenLocalDataSource -import org.sopt.and.data.remote.model.response.HobbyResponseDto -import org.sopt.and.di.ServicePool -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import org.sopt.and.domain.repository.TokenRepository +import org.sopt.and.domain.repository.UserRepository import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( - private val tokenLocalDataSource: TokenLocalDataSource + private val tokenRepository: TokenRepository, + private val userRepository: UserRepository ) : ViewModel() { - private val userService by lazy { ServicePool.userService } + private val token = tokenRepository.getToken() private val _userHobbyState = MutableStateFlow(UserHobbyState.Idle) val userHobbyState: StateFlow = _userHobbyState + private val _hobbySearchState = MutableStateFlow(HobbySearchState.Idle) + val hobbySearchState: StateFlow = _hobbySearchState + + private val _userInfoUpdateState = MutableStateFlow(UserInfoUpdateState.Idle) + val userInfoUpdateState: StateFlow = _userInfoUpdateState + fun getUserHobby() { + _userHobbyState.value = UserHobbyState.Loading + viewModelScope.launch { + val result = userRepository.getMyHobby(token = token) + _userHobbyState.value = result.fold( + onSuccess = { + UserHobbyState.Success(it) + }, + onFailure = { + UserHobbyState.Failure(it.localizedMessage ?: "에러 발생") + } + ) + } + } + + fun getOthersHobby(userNo: String) { + _hobbySearchState.value = HobbySearchState.Loading viewModelScope.launch { - userService.getMyHobby(token = tokenLocalDataSource.token).enqueue(object : - Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - _userHobbyState.value = - UserHobbyState.Success(response.body()?.result!!.hobby) - } else { - _userHobbyState.value = UserHobbyState.Failure(response.message()) - } + val result = userRepository.getOthersHobby( + token = token, + userNo = userNo.toInt() + ) + _hobbySearchState.value = result.fold( + onSuccess = { + HobbySearchState.Success(it) + }, + onFailure = { + HobbySearchState.Failure(it.localizedMessage ?: "에러 발생") } + ) + } + } - override fun onFailure( - call: Call, - t: Throwable - ) { - _userHobbyState.value = UserHobbyState.Failure(t.message.toString()) + fun updateUserInfo(password: String?, hobby: String?) { + _userInfoUpdateState.value = UserInfoUpdateState.Loading + viewModelScope.launch { + val result = userRepository.updateUserInfo( + token = token, + password = password, + hobby = hobby + ) + _userInfoUpdateState.value = result.fold( + onSuccess = { + UserInfoUpdateState.Success + }, + onFailure = { + UserInfoUpdateState.Failure(it.localizedMessage ?: "에러발생") } - } ) } } + + fun resetUserInfoUpdateState() { + _userInfoUpdateState.value = UserInfoUpdateState.Idle + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt index 4ae120b..e72fddb 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt @@ -2,6 +2,7 @@ package org.sopt.and.presentation.ui.main.screen import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -15,6 +16,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape 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.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -24,10 +31,40 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.and.R @Composable -fun MyPageScreen(userHobby: String) { +fun MyPageRoute( + mainViewModel: MainViewModel = hiltViewModel(), + navigateToSetting: () -> Unit +) { + val userHobbyState by mainViewModel.userHobbyState.collectAsState() + var userHobby by remember { mutableStateOf("") } + + LaunchedEffect(Unit) { + mainViewModel.getUserHobby() + } + + when (userHobbyState) { + is UserHobbyState.Success -> { + userHobby = (userHobbyState as UserHobbyState.Success).result.hobby + } + + else -> {} + } + + MyPageScreen( + userHobby = userHobby, + navigateToSetting = navigateToSetting + ) +} + +@Composable +fun MyPageScreen( + userHobby: String, + navigateToSetting: () -> Unit, +) { Column( modifier = Modifier .fillMaxWidth() @@ -67,6 +104,7 @@ fun MyPageScreen(userHobby: String) { modifier = Modifier .padding(8.dp) .size(28.dp) + .clickable(onClick = navigateToSetting) ) } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt index ce42cae..a7a4285 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt @@ -1,27 +1,147 @@ package org.sopt.and.presentation.ui.main.screen import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import org.sopt.and.presentation.ui.common.WavveTextField +import org.sopt.and.ui.theme.ANDANDROIDTheme @Composable -fun SearchScreen() { +fun SearchRoute( + mainViewModel: MainViewModel = hiltViewModel() +) { + val hobbySearchState by mainViewModel.hobbySearchState.collectAsState() + + SearchScreen( + hobbySearchState = hobbySearchState, + onSearchClick = { userNo -> mainViewModel.getOthersHobby(userNo) } + ) +} + +@Composable +fun SearchScreen( + hobbySearchState: HobbySearchState, + onSearchClick: (String) -> Unit +) { + var inputSearch by remember { mutableStateOf("") } + val isInputAvailable = inputSearch.toIntOrNull() != null + Column( modifier = Modifier .fillMaxSize() - .background(color = Color(0xFF161616)), - verticalArrangement = Arrangement.Center, + .background(color = Color(0xFF121212)) + .padding(30.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "검색화면", + modifier = Modifier.padding(bottom = 80.dp), + fontSize = 20.sp, color = Color(0xFFCCCCCC) ) + Row( + modifier = Modifier.padding(bottom = 100.dp), + verticalAlignment = Alignment.CenterVertically + ) { + WavveTextField( + value = inputSearch, + onValueChange = { newValue -> inputSearch = newValue }, + modifier = Modifier + .weight(1f) + .clip(shape = RoundedCornerShape(8.dp)) + .background(color = Color(0XFF3B5999)), + hint = "검색할 userNo을 입력하세요" + ) + Spacer(modifier = Modifier.width(8.dp)) + Box( + modifier = Modifier + .wrapContentWidth() + .clip(shape = RoundedCornerShape(8.dp)) + .background(color = if (isInputAvailable) Color.Green else Color.Red) + .clickable( + enabled = isInputAvailable, + onClick = { onSearchClick(inputSearch) } + ), + contentAlignment = Alignment.Center, + content = { + Text( + text = "검색하기", + modifier = Modifier.padding(horizontal = 10.dp, vertical = 16.dp), + fontSize = 14.sp + ) + } + ) + } + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .clip(shape = RoundedCornerShape(12.dp)) + .background(color = Color.White), + contentAlignment = Alignment.Center, + content = { + when (hobbySearchState) { + is HobbySearchState.Success -> { + Text( + text = "해당 유저의 취미는\n" + + "${hobbySearchState.result.hobby}입니다.", + modifier = Modifier.padding(12.dp) + ) + } + + is HobbySearchState.Failure -> { + Text( + text = "해당 유저의 취미를\n" + + "불러 오는데 실패했습니다.", + modifier = Modifier.padding(12.dp) + ) + } + + else -> { + Text( + text = "검색어를 입력하세요.", + modifier = Modifier.padding(12.dp) + ) + } + } + + } + ) + } +} + +@Preview(showBackground = true) +@Composable +fun ShowSearchScreen() { + ANDANDROIDTheme { + SearchScreen( + hobbySearchState = HobbySearchState.Idle, + onSearchClick = {} + ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SettingScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SettingScreen.kt new file mode 100644 index 0000000..6eeb013 --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SettingScreen.kt @@ -0,0 +1,145 @@ +package org.sopt.and.presentation.ui.main.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import org.sopt.and.presentation.ui.common.WavveTextField +import org.sopt.and.presentation.util.showToast +import org.sopt.and.ui.theme.ANDANDROIDTheme + +@Composable +fun SettingRoute( + mainViewModel: MainViewModel = hiltViewModel() +) { + val userInfoUpdateState by mainViewModel.userInfoUpdateState.collectAsState() + + SettingScreen( + userInfoUpdateState = userInfoUpdateState, + resetUserInfoUpdateState = { mainViewModel.resetUserInfoUpdateState() }, + onUserInfoChangeClick = { password, hobby -> mainViewModel.updateUserInfo(password, hobby) } + ) +} + +@Composable +fun SettingScreen( + userInfoUpdateState: UserInfoUpdateState, + resetUserInfoUpdateState: () -> Unit, + onUserInfoChangeClick: (String?, String?) -> Unit +) { + val context = LocalContext.current + var inputPassword by remember { mutableStateOf("") } + var inputHobby by remember { mutableStateOf("") } + var isButtonEnabled = inputPassword.isNotBlank() || inputHobby.isNotBlank() + + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color(0xFF161616)) + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "회원정보 변경", + modifier = Modifier.padding(bottom = 80.dp), + fontSize = 20.sp, + color = Color(0xFFCCCCCC) + ) + WavveTextField( + value = inputPassword, + onValueChange = { newValue -> inputPassword = newValue }, + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(6.dp)) + .background(color = Color(0xFF262626)), + hint = "변경할 비밀번호 (8자 이하)" + ) + Spacer(Modifier.height(8.dp)) + WavveTextField( + value = inputHobby, + onValueChange = { newValue -> inputHobby = newValue }, + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(6.dp)) + .background(color = Color(0xFF262626)), + hint = "변경할 취미 (8자 이하)" + ) + Spacer(Modifier.height(24.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .clip(shape = RoundedCornerShape(20.dp)) + .background(color = Color(0xFF1352F9)) + .clickable( + enabled = isButtonEnabled, + onClick = { + if (inputPassword.isBlank()) onUserInfoChangeClick(null, inputHobby) + else if (inputHobby.isBlank()) onUserInfoChangeClick(inputPassword, null) + else onUserInfoChangeClick(inputPassword, inputHobby) } + ), + contentAlignment = Alignment.Center, + content = { + Text( + text = "변경하기", + modifier = Modifier.padding(vertical = 12.dp), + color = Color.White + ) + } + ) + when (userInfoUpdateState) { + is UserInfoUpdateState.Success -> { + showToast( + context = context, + message = "회원정보 변경에 성공했습니다." + ) + resetUserInfoUpdateState() + } + + is UserInfoUpdateState.Failure -> { + showToast( + context = context, + message = "회원정보 변경에 실패했습니다." + ) + resetUserInfoUpdateState() + } + + else -> {} + } + } +} + +@Preview(showBackground = true) +@Composable +fun ShowSettingScreen() { + ANDANDROIDTheme { + SettingScreen( + userInfoUpdateState = UserInfoUpdateState.Idle, + resetUserInfoUpdateState = {}, + onUserInfoChangeClick = { _, _ -> } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt index fa33bab..469b079 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt @@ -1,7 +1,10 @@ package org.sopt.and.presentation.ui.main.screen +import org.sopt.and.domain.model.Hobby + sealed class UserHobbyState { data object Idle: UserHobbyState() - data class Success(val hobby: String): UserHobbyState() + data object Loading: UserHobbyState() + data class Success(val result: Hobby): UserHobbyState() data class Failure(val errorMessage: String): UserHobbyState() } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserInfoUpdateState.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserInfoUpdateState.kt new file mode 100644 index 0000000..4bf1da3 --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserInfoUpdateState.kt @@ -0,0 +1,10 @@ +package org.sopt.and.presentation.ui.main.screen + +import org.sopt.and.domain.model.Hobby + +sealed class UserInfoUpdateState { + data object Idle: UserInfoUpdateState() + data object Loading: UserInfoUpdateState() + data object Success: UserInfoUpdateState() + data class Failure(val errorMessage: String): UserInfoUpdateState() +} \ No newline at end of file