From cda17c884bc695c9bf0140509d8998ac58eb9edf Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Wed, 13 Nov 2024 02:12:18 +0900 Subject: [PATCH 01/11] apply MVVM architecture --- app/src/main/AndroidManifest.xml | 20 +------ .../main/java/org/sopt/and/MainActivity.kt | 30 +++++----- .../LoginScreen/LoginScreen.kt} | 53 ++++++++--------- .../LoginScreen/LoginViewModel.kt | 47 +++++++++++++++ .../homeScreen/HomeScreen.kt} | 52 ++++++++-------- .../presentation/homeScreen/HomeViewModel.kt | 17 ++++++ .../and/presentation/main/UserViewModel.kt | 16 +++++ .../mypageScreen/MypageScreen.kt} | 56 +++--------------- .../mypageScreen/MypageViewModel.kt | 18 ++++++ .../searchScreen/SearchScreen.kt} | 2 +- .../signupScreen/SignUpScreen.kt} | 59 ++++++++----------- .../signupScreen/SignUpViewModel.kt | 45 ++++++++++++++ 12 files changed, 244 insertions(+), 171 deletions(-) rename app/src/main/java/org/sopt/and/{LoginActivity.kt => presentation/LoginScreen/LoginScreen.kt} (79%) create mode 100644 app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt rename app/src/main/java/org/sopt/and/{HomeActivity.kt => presentation/homeScreen/HomeScreen.kt} (81%) create mode 100644 app/src/main/java/org/sopt/and/presentation/homeScreen/HomeViewModel.kt create mode 100644 app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt rename app/src/main/java/org/sopt/and/{MyActivity.kt => presentation/mypageScreen/MypageScreen.kt} (61%) create mode 100644 app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt rename app/src/main/java/org/sopt/and/{SearchActivity.kt => presentation/searchScreen/SearchScreen.kt} (96%) rename app/src/main/java/org/sopt/and/{SignUpActivity.kt => presentation/signupScreen/SignUpScreen.kt} (75%) create mode 100644 app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c5ca09d..52b8cce 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,25 +12,7 @@ android:supportsRtl="true" android:theme="@style/Theme.ANDANDROID" tools:targetApi="31"> - - - - - - - - - - - - - + () - val email: LiveData = _email - - fun setEmail(newEmail: String) { - _email.value = newEmail - } -} class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -49,8 +48,8 @@ class MainActivity : ComponentActivity() { ){ composable { SignUpScreen( - navigateToLoginScreen = { - emailText, passwordText -> navController.navigate(LoginScreen(emailText, passwordText)) + navigateToLoginScreen = { emailText, passwordText -> + navController.navigate(LoginScreen(emailText, passwordText)) } ) } @@ -73,6 +72,7 @@ class MainActivity : ComponentActivity() { composable("home") { HomeScreen( navController = navController, + homeViewModel = HomeViewModel() ) } @@ -86,7 +86,7 @@ class MainActivity : ComponentActivity() { MypageScreen( navController = navController, - userViewModel = userViewModel + mypageViewModel = MypageViewModel() ) } diff --git a/app/src/main/java/org/sopt/and/LoginActivity.kt b/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginScreen.kt similarity index 79% rename from app/src/main/java/org/sopt/and/LoginActivity.kt rename to app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginScreen.kt index ca074c4..a868356 100644 --- a/app/src/main/java/org/sopt/and/LoginActivity.kt +++ b/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginScreen.kt @@ -1,6 +1,5 @@ -package org.sopt.and +package org.sopt.and.presentation.LoginScreen -import androidx.activity.ComponentActivity import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -20,12 +19,11 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -34,6 +32,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import org.sopt.and.presentation.signupScreen.EmailValidCheck +import org.sopt.and.presentation.signupScreen.PasswordValidCheck +import org.sopt.and.R +import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection import org.sopt.and.ui.theme.ANDANDROIDTheme @@ -53,19 +55,15 @@ fun LoginScreen( emailText: String, passwordText: String, navigateToHomeScreen: () -> Unit, - userViewModel: UserViewModel = viewModel() + userViewModel: UserViewModel = viewModel(), + loginViewModel: LoginViewModel = viewModel() ) { - var inputEmail: String = "" - var inputPassword: String = "" - - var emailState = remember { mutableStateOf(inputEmail) } - var passwordState = remember { mutableStateOf(inputPassword) } - - var isEmailValid = remember { mutableStateOf(true) } - var isPasswordValid = remember { mutableStateOf(true) } - - var shouldShowPassword = remember { mutableStateOf(false) } + var emailState = loginViewModel.emailState.collectAsState().value + var passwordState = loginViewModel.passwordState.collectAsState().value + var isEmailValid = loginViewModel.isEmailValid.collectAsState().value + var isPasswordValid = loginViewModel.isPasswordValid.collectAsState().value + var shouldShowPassword = loginViewModel.shouldShowPassword.collectAsState().value Scaffold( modifier = modifier, @@ -94,13 +92,13 @@ fun LoginScreen( // Email 입력 필드 SignUpTextField( - text = emailState.value, + text = emailState, onValueChange = { newValue -> - emailState.value = newValue - isEmailValid.value = EmailValidCheck(emailState.value) + loginViewModel.onEmailChange(newValue) + isEmailValid = EmailValidCheck(emailState) }, fieldType = "Email", - conditionCheck = isEmailValid.value, + conditionCheck = isEmailValid, errMessage = "올바른 이메일 형식이 아닙니다.", placeholder = "wavve@example.com", ) @@ -109,18 +107,19 @@ fun LoginScreen( // Password 입력 필드 SignUpTextField( - text = passwordState.value, + text = passwordState, onValueChange = { newValue -> - passwordState.value = newValue - isPasswordValid.value = PasswordValidCheck(passwordState.value) + loginViewModel.onPasswordChange(newValue) + isPasswordValid = PasswordValidCheck(passwordState) }, fieldType = "Password", - conditionCheck = isPasswordValid.value, + conditionCheck = isPasswordValid, errMessage = "올바른 비밀번호 형식이 아닙니다.", placeholder = "Wavve 비밀번호 설정", - shouldShowPassword = shouldShowPassword.value, + shouldShowPassword = shouldShowPassword, onPasswordVisibilityChange = { - shouldShowPassword.value = !shouldShowPassword.value +// shouldShowPassword = !shouldShowPassword + loginViewModel.togglePasswordVisibility() }, ) @@ -132,7 +131,7 @@ fun LoginScreen( var loginMessage = "" var loginSuccessFlag = 0 - if (emailState.value == emailText && passwordState.value == passwordText) { + if (loginViewModel.isLoginValid(emailText, passwordText)) { loginMessage = "로그인 성공" loginSuccessFlag = 1 } else { @@ -143,7 +142,7 @@ fun LoginScreen( val snackbarResult = snackbarHostState.showSnackbar(loginMessage) if (loginSuccessFlag == 1 && snackbarResult == SnackbarResult.Dismissed) { - userViewModel.setEmail(emailState.value) + userViewModel.setEmail(emailState) navigateToHomeScreen() } } diff --git a/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt new file mode 100644 index 0000000..6192880 --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt @@ -0,0 +1,47 @@ +package org.sopt.and.presentation.LoginScreen + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.sopt.and.presentation.signupScreen.EmailValidCheck +import org.sopt.and.presentation.signupScreen.PasswordValidCheck + +class LoginViewModel : ViewModel() { + + private val _emailState = MutableStateFlow("") + val emailState: StateFlow = _emailState + + private val _passwordState = MutableStateFlow("") + val passwordState: StateFlow = _passwordState + + private val _isEmailValid = MutableStateFlow(false) + val isEmailValid: StateFlow = _isEmailValid + + private val _isPasswordValid = MutableStateFlow(false) + val isPasswordValid: StateFlow = _isPasswordValid + + private val _shouldShowPassword = MutableStateFlow(false) + val shouldShowPassword: StateFlow = _shouldShowPassword + + //이메일 입력 시 입력한 글자 표시 + fun onEmailChange(newEmail: String) { + _emailState.value = newEmail + _isEmailValid.value = EmailValidCheck(newEmail) + } + + //비밀번호 입력 시 입력한 글자 표시 + fun onPasswordChange(newPassword: String) { + _passwordState.value = newPassword + _isPasswordValid.value = PasswordValidCheck(newPassword) + } + + // 비밀번호 표시 여부 바꾸기 + fun togglePasswordVisibility() { + _shouldShowPassword.value = !_shouldShowPassword.value + } + + // 로그인 검증 로직 + fun isLoginValid(emailText: String, passwordText: String): Boolean { + return _emailState.value == emailText && _passwordState.value == passwordText + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/HomeActivity.kt b/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt similarity index 81% rename from app/src/main/java/org/sopt/and/HomeActivity.kt rename to app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt index c004ba0..0026dc2 100644 --- a/app/src/main/java/org/sopt/and/HomeActivity.kt +++ b/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package org.sopt.and +package org.sopt.and.presentation.homeScreen import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image @@ -27,25 +27,29 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import kotlinx.serialization.Serializable +import org.sopt.and.presentation.mypageScreen.MypageScreen +import org.sopt.and.presentation.searchScreen.SearchScreen import org.sopt.and.ui.components.BottomBar.CustomBottomAppBar import org.sopt.and.ui.components.HomeScreen.HomeLazyRow import org.sopt.and.ui.components.TopBar.CustomTopAppBar import org.sopt.and.ui.components.TopBar.CustomTopAppBarSecond import org.sopt.and.ui.theme.ANDANDROIDTheme -@Serializable -data object HomeScreen + +//@Serializable +//data object HomeScreen @OptIn(ExperimentalFoundationApi::class) @Composable fun HomeScreen( modifier: Modifier = Modifier, navController: NavController, // navController를 넘겨 받아 사용 + homeViewModel: HomeViewModel = viewModel() ) { val context = LocalContext.current val scrollState = rememberScrollState() @@ -70,15 +74,7 @@ fun HomeScreen( .padding(all = 10.dp) ) { - val images = listOf( - R.drawable.food_pic1, - R.drawable.food_pic2, - R.drawable.food_pic3, - R.drawable.food_pic4, - R.drawable.food_pic5 - ) - - val pagerState = rememberPagerState { images.size } + val pagerState = rememberPagerState { homeViewModel.mainPagerImages.size } HorizontalPager( state = pagerState, @@ -91,7 +87,7 @@ fun HomeScreen( .fillMaxSize() .padding(16.dp) .clip(RoundedCornerShape(16.dp)), - painter = painterResource(id = images[idx]), + painter = painterResource(id = homeViewModel.mainPagerImages[idx]), contentDescription = "imagePager", contentScale = ContentScale.Crop ) @@ -99,7 +95,7 @@ fun HomeScreen( HomeLazyRow( title = "믿고 보는 웨이브 에디터 추천작", - images = images, + images = homeViewModel.mainPagerImages, height = 230, width = 140, ) @@ -107,7 +103,7 @@ fun HomeScreen( HomeLazyRow( title = "실시간 인기 콘텐츠", - images = images, + images = homeViewModel.mainPagerImages, height = 230, width = 140, ) @@ -115,7 +111,7 @@ fun HomeScreen( HomeLazyRow( title = "오직 웨이브에서", - images = images, + images = homeViewModel.mainPagerImages, height = 230, width = 140, ) @@ -123,7 +119,7 @@ fun HomeScreen( HomeLazyRow( title = "오늘의 TOP 20", - images = images, + images = homeViewModel.mainPagerImages, height = 260, width = 180, ) @@ -131,7 +127,7 @@ fun HomeScreen( HomeLazyRow( title = "당한 대로 갚아줄게", - images = images, + images = homeViewModel.mainPagerImages, height = 230, width = 140, ) @@ -146,6 +142,7 @@ fun HomeScreen( @Composable fun HomeScreenPreview() { val navController = rememberNavController() + val homeViewModel = HomeViewModel() ANDANDROIDTheme { Scaffold( @@ -174,14 +171,19 @@ fun HomeScreenPreview() { startDestination = "home", ){ composable("home") {HomeScreen( - navController = navController - )} - composable("search") {SearchScreen( - navController = navController - )} - composable("profile") {MypageScreen( navController = navController, + homeViewModel = homeViewModel )} + composable("search") { + SearchScreen( + navController = navController + ) + } + composable("profile") { + MypageScreen( + navController = navController, + ) + } } } diff --git a/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeViewModel.kt b/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeViewModel.kt new file mode 100644 index 0000000..93b63ad --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeViewModel.kt @@ -0,0 +1,17 @@ +package org.sopt.and.presentation.homeScreen + +import androidx.compose.foundation.pager.PagerState +import androidx.lifecycle.ViewModel +import org.sopt.and.R + +class HomeViewModel : ViewModel() { + + val mainPagerImages = listOf( + R.drawable.food_pic1, + R.drawable.food_pic2, + R.drawable.food_pic3, + R.drawable.food_pic4, + R.drawable.food_pic5 + ) + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt b/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt new file mode 100644 index 0000000..e01fb2d --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt @@ -0,0 +1,16 @@ +package org.sopt.and.presentation.main + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class UserViewModel : ViewModel() { + + private val _email = MutableLiveData() + val email: LiveData = _email + + fun setEmail(newEmail: String) { + _email.value = newEmail + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/MyActivity.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt similarity index 61% rename from app/src/main/java/org/sopt/and/MyActivity.kt rename to app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt index 3790d39..1c66e3f 100644 --- a/app/src/main/java/org/sopt/and/MyActivity.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt @@ -1,82 +1,40 @@ -package org.sopt.and +package org.sopt.and.presentation.mypageScreen -import android.content.Intent -import android.graphics.Paint -import android.graphics.drawable.Icon -import android.os.Bundle -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement 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.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Person -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.filled.Warning -import androidx.compose.material.icons.outlined.Notifications -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import kotlinx.serialization.Serializable import org.sopt.and.ui.components.BottomBar.CustomBottomAppBar -import org.sopt.and.ui.components.BottomBar.NavIcon import org.sopt.and.ui.components.MypageScreen.MyPageProfileSection import org.sopt.and.ui.components.MypageScreen.MyPageProfileSection2 import org.sopt.and.ui.components.MypageScreen.MyPageSubSection -import org.sopt.and.ui.components.TopBar.CustomTopAppBar import org.sopt.and.ui.theme.ANDANDROIDTheme import androidx.compose.runtime.livedata.observeAsState +import org.sopt.and.presentation.main.UserViewModel @Composable fun MypageScreen( navController: NavController, - userViewModel: UserViewModel = viewModel() + //userViewModel: UserViewModel = viewModel(), //viewModel이 짬뽕됐는데 뭘 어디서 써야하는지..? 명확X.. + mypageViewModel: MypageViewModel = viewModel() ) { - val context = LocalContext.current - val emailText = userViewModel.email.observeAsState("").value + val user = mypageViewModel.user.collectAsState().value Scaffold( bottomBar = { @@ -90,7 +48,7 @@ fun MypageScreen( .padding(innerPadding) ) { MyPageProfileSection( - deliveredEmail = emailText + deliveredEmail = mypageViewModel.getUserEmail() ) Spacer(modifier = Modifier.height(0.5.dp)) MyPageProfileSection2( diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt new file mode 100644 index 0000000..9ace8d1 --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -0,0 +1,18 @@ +package org.sopt.and.presentation.mypageScreen + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +import org.sopt.and.presentation.main.UserViewModel + +class MypageViewModel : ViewModel() { + + private val _user = MutableStateFlow(UserViewModel()) + val user: StateFlow = _user + + fun getUserEmail(): String { + return _user.value.email.toString() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/SearchActivity.kt b/app/src/main/java/org/sopt/and/presentation/searchScreen/SearchScreen.kt similarity index 96% rename from app/src/main/java/org/sopt/and/SearchActivity.kt rename to app/src/main/java/org/sopt/and/presentation/searchScreen/SearchScreen.kt index 367814f..7e6bb06 100644 --- a/app/src/main/java/org/sopt/and/SearchActivity.kt +++ b/app/src/main/java/org/sopt/and/presentation/searchScreen/SearchScreen.kt @@ -1,4 +1,4 @@ -package org.sopt.and +package org.sopt.and.presentation.searchScreen import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize diff --git a/app/src/main/java/org/sopt/and/SignUpActivity.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt similarity index 75% rename from app/src/main/java/org/sopt/and/SignUpActivity.kt rename to app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt index 166e5f1..74e7db8 100644 --- a/app/src/main/java/org/sopt/and/SignUpActivity.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt @@ -1,26 +1,18 @@ -package org.sopt.and +package org.sopt.and.presentation.signupScreen -import android.content.Intent -import android.os.Bundle import android.util.Patterns import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement 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.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -30,10 +22,12 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.serialization.Serializable import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection import org.sopt.and.ui.theme.ANDANDROIDTheme +import kotlin.math.sign @Serializable data object SignUpScreen @@ -59,20 +53,17 @@ fun PasswordValidCheck(password: String): Boolean { fun SignUpScreen( modifier: Modifier = Modifier, navigateToLoginScreen: (emailText: String, passwordText: String) -> Unit, + signUpViewModel: SignUpViewModel = viewModel() ) { val context = LocalContext.current - - var emailFlag = 0 - var passwordFlag = 0 //8~20자 이내 조건 확인 var toastMessage = "" - var emailText = remember { mutableStateOf("") } - var passwordText = remember { mutableStateOf("") } - - var shouldShowPassword = remember {mutableStateOf(false)} - var isEmailValid = remember { mutableStateOf(true) } - var isPasswordValid = remember { mutableStateOf(true) } + var emailText = signUpViewModel.emailText.collectAsState().value + var passwordText = signUpViewModel.passwordText.collectAsState().value + var isEmailValid = signUpViewModel.isEmailValid.collectAsState().value + var isPasswordValid = signUpViewModel.isPasswordValid.collectAsState().value + var shouldShowPassword = signUpViewModel.shouldShowPassword.collectAsState().value Column( modifier = Modifier @@ -100,13 +91,13 @@ fun SignUpScreen( Spacer(modifier = Modifier.weight(0.25f)) SignUpTextField( - text = emailText.value, + text = emailText, onValueChange = { newValue -> - emailText.value = newValue - isEmailValid.value = EmailValidCheck(emailText.value) + signUpViewModel.onEmailChange(newValue) + isEmailValid = EmailValidCheck(emailText) }, fieldType = "Email", - conditionCheck = isEmailValid.value, + conditionCheck = isEmailValid, errMessage = "올바른 이메일 형식이 아닙니다.", placeholder = "wavve@example.com", descriptionText = "로그인, 비밀번호 찾기, 알림에 사용되니 정확한 이메일을 입력해주세요.", @@ -115,18 +106,18 @@ fun SignUpScreen( Spacer(modifier = Modifier.weight(0.15f)) SignUpTextField( - text = passwordText.value, + text = passwordText, onValueChange = { newValue -> - passwordText.value = newValue - isPasswordValid.value = PasswordValidCheck(passwordText.value) + signUpViewModel.onPasswordChange(newValue) + isPasswordValid = PasswordValidCheck(passwordText) }, fieldType = "Password", - conditionCheck = isPasswordValid.value, + conditionCheck = isPasswordValid, errMessage = "올바른 비밀번호 형식이 아닙니다.", placeholder = "Wavve 비밀번호 설정", - shouldShowPassword = shouldShowPassword.value, + shouldShowPassword = shouldShowPassword, onPasswordVisibilityChange = { - shouldShowPassword.value = !shouldShowPassword.value + signUpViewModel.togglePasswordVisibility() }, descriptionText = "비밀번호는 8~20자 이내로 영문 대소문자, 숫자, 특수문자 중 3가지 이상 혼용하여 입력해 주세요.", ) @@ -145,24 +136,22 @@ fun SignUpScreen( .clickable { //이메일 형식 조건 검사 - if (!EmailValidCheck(emailText.value)) { - emailFlag = 1 + if (!EmailValidCheck(emailText)) { toastMessage = "형식에 맞는 이메일을 입력하세요" } //비밀번호 형식 조건 검사 - if (!PasswordValidCheck(passwordText.value)) { - passwordFlag = 1 + if (!PasswordValidCheck(passwordText)) { toastMessage = "조건에 맞는 비밀번호를 사용하세요" } - if (emailFlag == 0 && passwordFlag == 0) { + if (signUpViewModel.isSignUpValid()) { toastMessage = "로그인 되었습니다" //전달해줄 인자를 이 안에 넣으면 되는 듯.. - navigateToLoginScreen(emailText.value, passwordText.value) + navigateToLoginScreen(emailText, passwordText) println("네비게이트는 지남...") } diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt new file mode 100644 index 0000000..72291d3 --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt @@ -0,0 +1,45 @@ +package org.sopt.and.presentation.signupScreen + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class SignUpViewModel : ViewModel() { + + private val _emailText = MutableStateFlow("") + val emailText: StateFlow = _emailText + + private val _passwordText = MutableStateFlow("") + val passwordText: StateFlow = _passwordText + + private val _isEmailValid = MutableStateFlow(true) + val isEmailValid: StateFlow = _isEmailValid + + private val _isPasswordValid = MutableStateFlow(true) + val isPasswordValid: StateFlow = _isPasswordValid + + private val _shouldShowPassword = MutableStateFlow(false) + val shouldShowPassword: StateFlow = _shouldShowPassword + + + // 이메일 입력 시 입력한 값 보이기 + fun onEmailChange(newEmail: String) { + _emailText.value = newEmail + _isEmailValid.value = EmailValidCheck(newEmail) + } + + // 비밀번호 입력 시 입력한 값 보이기 + fun onPasswordChange(newPassword: String) { + _passwordText.value = newPassword + _isPasswordValid.value = PasswordValidCheck(newPassword) + } + + // 비밀번호 노출 상태 변경 시 + fun togglePasswordVisibility() { + _shouldShowPassword.value = !_shouldShowPassword.value + } + + fun isSignUpValid(): Boolean { + return _isEmailValid.value && _isPasswordValid.value + } +} \ No newline at end of file From 113b9fa3247fdee6e05b2f62798ce7a2cdf58216 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Fri, 15 Nov 2024 23:48:23 +0900 Subject: [PATCH 02/11] api connecting --- app/build.gradle.kts | 19 ++++- app/src/main/AndroidManifest.xml | 3 + .../main/java/org/sopt/and/MainActivity.kt | 20 +++-- .../and/model/dto/RequestCreateUserDto.kt | 14 ++++ .../model/dto/ResponseCreateUserFailDto.kt | 16 ++++ .../model/dto/ResponseCreateUserSuccessDto.kt | 16 ++++ .../model/dto/ResponseCreateUserWrapperDto.kt | 14 ++++ .../org/sopt/and/model/network/ApiFactory.kt | 37 +++++++++ .../org/sopt/and/model/network/UserService.kt | 23 ++++++ .../LoginScreen.kt | 43 +++++----- .../LoginViewModel.kt | 24 +++--- .../and/presentation/main/UserViewModel.kt | 18 ++++- .../presentation/mypageScreen/MypageScreen.kt | 7 +- .../mypageScreen/MypageViewModel.kt | 4 +- .../presentation/signupScreen/SignUpScreen.kt | 79 ++++++++++++------- .../signupScreen/SignUpViewModel.kt | 70 +++++++++++++--- .../MypageScreen/MyPageProfileSection.kt | 4 +- .../SignUpandLogIn/SignUpTextField.kt | 2 +- gradle.properties | 3 +- gradle/libs.versions.toml | 8 ++ 20 files changed, 330 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/network/ApiFactory.kt create mode 100644 app/src/main/java/org/sopt/and/model/network/UserService.kt rename app/src/main/java/org/sopt/and/presentation/{LoginScreen => loginScreen}/LoginScreen.kt (82%) rename app/src/main/java/org/sopt/and/presentation/{LoginScreen => loginScreen}/LoginViewModel.kt (58%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 61fde81..ec19294 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -5,18 +7,22 @@ plugins { alias(libs.plugins.kotlin.serialization) } +val properties = Properties().apply { + load(project.rootProject.file("local.properties").inputStream()) +} + android { namespace = "org.sopt.and" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "org.sopt.and" minSdk = 28 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "BASE_URL", properties["base.url"].toString()) } buildTypes { @@ -37,6 +43,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -65,4 +72,10 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + implementation(platform(libs.okhttp.bom)) + implementation(libs.okhttp) + implementation(libs.okhttp.logging.interceptor) + implementation(libs.retrofit) + implementation(libs.retrofit.kotlin.serialization.converter) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 52b8cce..bb70edb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + val navController = rememberNavController() val userViewModel: UserViewModel = viewModel() + val mypageViewModel : MypageViewModel = viewModel() NavHost( navController = navController, @@ -48,8 +44,8 @@ class MainActivity : ComponentActivity() { ){ composable { SignUpScreen( - navigateToLoginScreen = { emailText, passwordText -> - navController.navigate(LoginScreen(emailText, passwordText)) + navigateToLoginScreen = { userNameText, passwordText -> + navController.navigate(LoginScreen(userNameText, passwordText)) } ) } @@ -59,13 +55,14 @@ class MainActivity : ComponentActivity() { val scope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } LoginScreen( - emailText = item.emailText, + userNameText = item.userNameText, passwordText = item.passwordText, scope = scope, snackbarHostState = snackbarHostState, navigateToHomeScreen = { navController.navigate("home") - } + }, + userViewModel = userViewModel ) } @@ -86,7 +83,8 @@ class MainActivity : ComponentActivity() { MypageScreen( navController = navController, - mypageViewModel = MypageViewModel() + userViewModel = userViewModel, + mypageViewModel = mypageViewModel ) } diff --git a/app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt b/app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt new file mode 100644 index 0000000..0074149 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt @@ -0,0 +1,14 @@ +package org.sopt.and.model.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestCreateUserDto( + @SerialName("username") + val userName: String, + @SerialName("password") + val password: String, + @SerialName("hobby") + val hobby: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt b/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt new file mode 100644 index 0000000..9b62f1d --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt @@ -0,0 +1,16 @@ +package org.sopt.and.model.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseCreateUserFailDto( + @SerialName("failed") + val code: FailureResult +) + +@Serializable +data class FailureResult( + @SerialName("code") + val no: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt b/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt new file mode 100644 index 0000000..e117dad --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt @@ -0,0 +1,16 @@ +package org.sopt.and.model.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseCreateUserSuccessDto( + @SerialName("success") + val result: UserResult +) + +@Serializable +data class UserResult( + @SerialName("no") + val no: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt b/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt new file mode 100644 index 0000000..accd6ea --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt @@ -0,0 +1,14 @@ +package org.sopt.and.model.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseCreateUserWrapperDto( //특정 유저 한 명의 응답 DTO + @SerialName("success") + val data: ResponseCreateUserSuccessDto, + @SerialName("failed") + val support: ResponseCreateUserFailDto +) + + diff --git a/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt b/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt new file mode 100644 index 0000000..536e8aa --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt @@ -0,0 +1,37 @@ +package org.sopt.and.model.network + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.sopt.and.BuildConfig +import retrofit2.Retrofit + +object ApiFactory { + private const val BASE_URL: String = BuildConfig.BASE_URL + + private val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + private val client = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + + val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + inline fun create(): T = retrofit.create(T::class.java) +} + +//이 userService를, 서버를 붙이는 부분에서 사용해야 함. +//Viewmodel에서 이 userService에 접근하면 됨. +object ServicePool { + val userService = ApiFactory.create() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/network/UserService.kt b/app/src/main/java/org/sopt/and/model/network/UserService.kt new file mode 100644 index 0000000..3081c42 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/network/UserService.kt @@ -0,0 +1,23 @@ +package org.sopt.and.model.network + +import org.sopt.and.model.dto.RequestCreateUserDto +import org.sopt.and.model.dto.ResponseCreateUserWrapperDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +//HTTP 메서드를 정의해둔 인터페이스 == service라고 부름. + +interface UserService { + + //유저 회원가입 + @POST("/user") + fun signUpUser( + @Body request: RequestCreateUserDto + ): Call + //여기서 Call : 응답이 왔을 때 불려질 타입 + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginScreen.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt similarity index 82% rename from app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginScreen.kt rename to app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt index a868356..61060d3 100644 --- a/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt @@ -1,4 +1,4 @@ -package org.sopt.and.presentation.LoginScreen +package org.sopt.and.presentation.loginScreen import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -32,7 +32,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import org.sopt.and.presentation.signupScreen.EmailValidCheck +import org.sopt.and.presentation.signupScreen.StringInputValidCheck import org.sopt.and.presentation.signupScreen.PasswordValidCheck import org.sopt.and.R import org.sopt.and.presentation.main.UserViewModel @@ -43,7 +43,7 @@ import org.sopt.and.ui.theme.ANDANDROIDTheme @Serializable data class LoginScreen( - val emailText: String, + val userNameText: String, val passwordText: String ) @@ -52,16 +52,16 @@ fun LoginScreen( modifier: Modifier = Modifier.fillMaxSize(), scope: CoroutineScope, snackbarHostState: SnackbarHostState, - emailText: String, + userNameText: String, passwordText: String, navigateToHomeScreen: () -> Unit, userViewModel: UserViewModel = viewModel(), loginViewModel: LoginViewModel = viewModel() ) { - var emailState = loginViewModel.emailState.collectAsState().value + var userNameState = loginViewModel.userNameState.collectAsState().value var passwordState = loginViewModel.passwordState.collectAsState().value - var isEmailValid = loginViewModel.isEmailValid.collectAsState().value + var isUserNameValid = loginViewModel.isUserNameValid.collectAsState().value var isPasswordValid = loginViewModel.isPasswordValid.collectAsState().value var shouldShowPassword = loginViewModel.shouldShowPassword.collectAsState().value @@ -90,17 +90,17 @@ fun LoginScreen( ) } - // Email 입력 필드 + // UserName 입력 필드 SignUpTextField( - text = emailState, + text = userNameState, onValueChange = { newValue -> - loginViewModel.onEmailChange(newValue) - isEmailValid = EmailValidCheck(emailState) + loginViewModel.onUserNameChange(newValue) + isUserNameValid = StringInputValidCheck(userNameState) }, - fieldType = "Email", - conditionCheck = isEmailValid, - errMessage = "올바른 이메일 형식이 아닙니다.", - placeholder = "wavve@example.com", + fieldType = "UserName", + conditionCheck = isUserNameValid, + errMessage = "유저 이름은 7자 이하여야 합니다.", + placeholder = "유저 이름 (7자 이하)", ) Spacer(modifier = Modifier.weight(0.025f)) @@ -131,18 +131,25 @@ fun LoginScreen( var loginMessage = "" var loginSuccessFlag = 0 - if (loginViewModel.isLoginValid(emailText, passwordText)) { + if (loginViewModel.isLoginValid(userNameText, passwordText)) { loginMessage = "로그인 성공" loginSuccessFlag = 1 + + /* TODO: 로그인 성공한 유저네임 갖고, 저장된 유저의 정보 가져오기. + * 로그인 성공 시, response로 돌아오는 token 값을 넘겨주기 */ + + userViewModel.setUserName(userNameText) + + } else { - loginMessage = "알맞은 이메일과 비밀번호를 입력하세요" + loginMessage = "알맞은 유저 이름과 비밀번호를 입력하세요" } scope.launch { val snackbarResult = snackbarHostState.showSnackbar(loginMessage) if (loginSuccessFlag == 1 && snackbarResult == SnackbarResult.Dismissed) { - userViewModel.setEmail(emailState) + userViewModel.setUserName(userNameState) navigateToHomeScreen() } } @@ -186,7 +193,7 @@ fun LoginScreenPreview2() { LoginScreen( scope = scope, snackbarHostState = snackbarHostState, - emailText = "", + userNameText = "", passwordText = "", navigateToHomeScreen = {}, ) diff --git a/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt similarity index 58% rename from app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt rename to app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt index 6192880..be4da09 100644 --- a/app/src/main/java/org/sopt/and/presentation/LoginScreen/LoginViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt @@ -1,21 +1,21 @@ -package org.sopt.and.presentation.LoginScreen +package org.sopt.and.presentation.loginScreen import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.and.presentation.signupScreen.EmailValidCheck +import org.sopt.and.presentation.signupScreen.StringInputValidCheck import org.sopt.and.presentation.signupScreen.PasswordValidCheck class LoginViewModel : ViewModel() { - private val _emailState = MutableStateFlow("") - val emailState: StateFlow = _emailState + private val _userNameState = MutableStateFlow("") + val userNameState: StateFlow = _userNameState private val _passwordState = MutableStateFlow("") val passwordState: StateFlow = _passwordState - private val _isEmailValid = MutableStateFlow(false) - val isEmailValid: StateFlow = _isEmailValid + private val _isUserNameValid = MutableStateFlow(false) + val isUserNameValid: StateFlow = _isUserNameValid private val _isPasswordValid = MutableStateFlow(false) val isPasswordValid: StateFlow = _isPasswordValid @@ -23,10 +23,10 @@ class LoginViewModel : ViewModel() { private val _shouldShowPassword = MutableStateFlow(false) val shouldShowPassword: StateFlow = _shouldShowPassword - //이메일 입력 시 입력한 글자 표시 - fun onEmailChange(newEmail: String) { - _emailState.value = newEmail - _isEmailValid.value = EmailValidCheck(newEmail) + //유저네임 입력 시 입력한 글자 표시 + fun onUserNameChange(newUserName: String) { + _userNameState.value = newUserName + _isUserNameValid.value = StringInputValidCheck(newUserName) } //비밀번호 입력 시 입력한 글자 표시 @@ -41,7 +41,7 @@ class LoginViewModel : ViewModel() { } // 로그인 검증 로직 - fun isLoginValid(emailText: String, passwordText: String): Boolean { - return _emailState.value == emailText && _passwordState.value == passwordText + fun isLoginValid(userNameText: String, passwordText: String): Boolean { + return _userNameState.value == userNameText && _passwordState.value == passwordText } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt b/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt index e01fb2d..e4b9a33 100644 --- a/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt @@ -3,14 +3,24 @@ package org.sopt.and.presentation.main import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.sopt.and.presentation.signupScreen.StringInputValidCheck class UserViewModel : ViewModel() { - private val _email = MutableLiveData() - val email: LiveData = _email + private val _userName = MutableStateFlow("") + val userName: StateFlow = _userName - fun setEmail(newEmail: String) { - _email.value = newEmail + private val _hobby = MutableStateFlow("") + val hobby: StateFlow = _hobby + + fun setUserName(newUserName: String) { + _userName.value = newUserName + } + + fun setHobby(newHobbyString: String) { + _hobby.value = newHobbyString } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt index 1c66e3f..d71774f 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt @@ -30,11 +30,12 @@ import org.sopt.and.presentation.main.UserViewModel @Composable fun MypageScreen( navController: NavController, - //userViewModel: UserViewModel = viewModel(), //viewModel이 짬뽕됐는데 뭘 어디서 써야하는지..? 명확X.. + userViewModel: UserViewModel = viewModel(), mypageViewModel: MypageViewModel = viewModel() ) { - val user = mypageViewModel.user.collectAsState().value + var userNameText = userViewModel.userName.collectAsState().value + //val userName = userViewModel.userName Scaffold( bottomBar = { @@ -48,7 +49,7 @@ fun MypageScreen( .padding(innerPadding) ) { MyPageProfileSection( - deliveredEmail = mypageViewModel.getUserEmail() + deliveredUserName = userNameText ) Spacer(modifier = Modifier.height(0.5.dp)) MyPageProfileSection2( diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt index 9ace8d1..10e74ba 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -11,8 +11,8 @@ class MypageViewModel : ViewModel() { private val _user = MutableStateFlow(UserViewModel()) val user: StateFlow = _user - fun getUserEmail(): String { - return _user.value.email.toString() + fun getUserName(): String { + return _user.value.userName.toString() } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt index 74e7db8..f907a21 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.serialization.Serializable +import org.sopt.and.model.dto.RequestCreateUserDto import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection import org.sopt.and.ui.theme.ANDANDROIDTheme @@ -32,15 +33,15 @@ import kotlin.math.sign @Serializable data object SignUpScreen -fun EmailValidCheck(email: String): Boolean { +fun StringInputValidCheck(newString: String): Boolean { var isValid = false - val inputStr : CharSequence = email - val pattern = Patterns.EMAIL_ADDRESS - val matcher = pattern.matcher(inputStr) - if(matcher.matches()){ - isValid = true + val inputStr : CharSequence = newString + + if(inputStr.length >= 8){ + return isValid + } else { + return !isValid } - return isValid } fun PasswordValidCheck(password: String): Boolean { @@ -52,17 +53,19 @@ fun PasswordValidCheck(password: String): Boolean { @Composable fun SignUpScreen( modifier: Modifier = Modifier, - navigateToLoginScreen: (emailText: String, passwordText: String) -> Unit, + navigateToLoginScreen: (userNameText: String, passwordText: String) -> Unit, signUpViewModel: SignUpViewModel = viewModel() ) { val context = LocalContext.current var toastMessage = "" - var emailText = signUpViewModel.emailText.collectAsState().value + var userNameText = signUpViewModel.userNameText.collectAsState().value var passwordText = signUpViewModel.passwordText.collectAsState().value - var isEmailValid = signUpViewModel.isEmailValid.collectAsState().value + var hobbyText = signUpViewModel.hobbyText.collectAsState().value + var isUserNameValid = signUpViewModel.isUserNameValid.collectAsState().value var isPasswordValid = signUpViewModel.isPasswordValid.collectAsState().value + var isHobbyValid = signUpViewModel.isHobbyValid.collectAsState().value var shouldShowPassword = signUpViewModel.shouldShowPassword.collectAsState().value Column( @@ -83,7 +86,7 @@ fun SignUpScreen( Spacer(modifier = Modifier.weight(0.35f)) Text( - "이메일과 비밀번호만으로\nWavve를 즐길 수 있어요!", + "유저 이름, 비밀번호, 취미 입력만으로\nWavve를 즐길 수 있어요!", color = Color.White, fontSize = 21.sp ) @@ -91,16 +94,16 @@ fun SignUpScreen( Spacer(modifier = Modifier.weight(0.25f)) SignUpTextField( - text = emailText, + text = userNameText, onValueChange = { newValue -> - signUpViewModel.onEmailChange(newValue) - isEmailValid = EmailValidCheck(emailText) + signUpViewModel.onUserNameChange(newValue) + isUserNameValid = StringInputValidCheck(userNameText) }, - fieldType = "Email", - conditionCheck = isEmailValid, - errMessage = "올바른 이메일 형식이 아닙니다.", - placeholder = "wavve@example.com", - descriptionText = "로그인, 비밀번호 찾기, 알림에 사용되니 정확한 이메일을 입력해주세요.", + fieldType = "userName", + conditionCheck = isUserNameValid, + errMessage = "유저 이름은 7자 이하여야 합니다.", + placeholder = "유저 이름 (7자 이하)", + descriptionText = "로그인, 비밀번호 찾기, 알림에 사용되니 정확하게 입력해주세요.", ) Spacer(modifier = Modifier.weight(0.15f)) @@ -122,6 +125,19 @@ fun SignUpScreen( descriptionText = "비밀번호는 8~20자 이내로 영문 대소문자, 숫자, 특수문자 중 3가지 이상 혼용하여 입력해 주세요.", ) + SignUpTextField( + text = hobbyText, + onValueChange = { newValue -> + signUpViewModel.onHobbyChange(newValue) + isHobbyValid = StringInputValidCheck(hobbyText) + }, + fieldType = "hobby", + conditionCheck = isHobbyValid, + errMessage = "취미은 7자 이하여야 합니다.", + placeholder = "취미 입력", + //descriptionText = "로그인, 비밀번호 찾기, 알림에 사용되니 정확하게 입력해주세요.", + ) + Spacer(modifier = Modifier.weight(0.5f)) SocialLoginSection(modifier = modifier) Spacer(modifier = Modifier.weight(1f)) @@ -135,9 +151,9 @@ fun SignUpScreen( .padding(vertical = 13.dp) .clickable { - //이메일 형식 조건 검사 - if (!EmailValidCheck(emailText)) { - toastMessage = "형식에 맞는 이메일을 입력하세요" + //유저 네임 형식 조건 검사 + if (!StringInputValidCheck(userNameText)) { + toastMessage = "형식에 맞는 유저 네임을 입력하세요" } @@ -148,11 +164,18 @@ fun SignUpScreen( if (signUpViewModel.isSignUpValid()) { - toastMessage = "로그인 되었습니다" + /* 새로 회원가입한 정보 저장 */ + val newUser = RequestCreateUserDto( + userName = userNameText, + password = passwordText, + hobby = hobbyText + ) + signUpViewModel.createNewUser(newUser) + + toastMessage = "회원가입에 성공하였습니다." + - //전달해줄 인자를 이 안에 넣으면 되는 듯.. - navigateToLoginScreen(emailText, passwordText) - println("네비게이트는 지남...") + navigateToLoginScreen(userNameText, passwordText) } Toast @@ -173,8 +196,8 @@ fun SignUpPreview() { ANDANDROIDTheme { SignUpScreen( navigateToLoginScreen = { - email, password -> - println("email: $email, password: $password") + userName, password -> + println("userName: $userName, password: $password") } ) } diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt index 72291d3..8092e77 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt @@ -1,31 +1,47 @@ package org.sopt.and.presentation.signupScreen +import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.sopt.and.model.dto.RequestCreateUserDto +import org.sopt.and.model.dto.ResponseCreateUserWrapperDto +import org.sopt.and.model.network.ServicePool +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response class SignUpViewModel : ViewModel() { - private val _emailText = MutableStateFlow("") - val emailText: StateFlow = _emailText + //회원가입 성공 시 서버로 create 요청 보내기 위함 + private val userService by lazy { ServicePool.userService } + + private val _userNameText = MutableStateFlow("") + val userNameText: StateFlow = _userNameText private val _passwordText = MutableStateFlow("") val passwordText: StateFlow = _passwordText - private val _isEmailValid = MutableStateFlow(true) - val isEmailValid: StateFlow = _isEmailValid + private val _hobbyText = MutableStateFlow("") + val hobbyText: StateFlow = _hobbyText + + private val _isUserNameValid = MutableStateFlow(true) + val isUserNameValid: StateFlow = _isUserNameValid private val _isPasswordValid = MutableStateFlow(true) val isPasswordValid: StateFlow = _isPasswordValid + private val _isHobbyValid = MutableStateFlow(true) + val isHobbyValid: StateFlow = _isHobbyValid + private val _shouldShowPassword = MutableStateFlow(false) val shouldShowPassword: StateFlow = _shouldShowPassword - // 이메일 입력 시 입력한 값 보이기 - fun onEmailChange(newEmail: String) { - _emailText.value = newEmail - _isEmailValid.value = EmailValidCheck(newEmail) + // 유저 네임 입력 시 입력한 값 보이기 + fun onUserNameChange(newUserName: String) { + _userNameText.value = newUserName + _isUserNameValid.value = StringInputValidCheck(newUserName) } // 비밀번호 입력 시 입력한 값 보이기 @@ -34,12 +50,48 @@ class SignUpViewModel : ViewModel() { _isPasswordValid.value = PasswordValidCheck(newPassword) } + // 비밀번호 입력 시 입력한 값 보이기 + fun onHobbyChange(newHobby: String) { + _hobbyText.value = newHobby + _isHobbyValid.value = StringInputValidCheck(newHobby) + } + // 비밀번호 노출 상태 변경 시 fun togglePasswordVisibility() { _shouldShowPassword.value = !_shouldShowPassword.value } fun isSignUpValid(): Boolean { - return _isEmailValid.value && _isPasswordValid.value + return _isUserNameValid.value && _isPasswordValid.value + } + + fun createNewUser(request: RequestCreateUserDto) { + val TAG = "UserService" + + ServicePool.userService.signUpUser(request).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val body = response.body() + if (body != null) { + Log.d(TAG, "User successfully created with no: ${body.data.result.no}") + } else { + Log.e(TAG, "Response body is null") + } + } else { + Log.e(TAG, "Failed with error code: ${response.code()}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "Error during API call: ${t.message}") + } + }) } + + + + } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt b/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt index 1fc8269..a1911f9 100644 --- a/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt +++ b/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt @@ -27,7 +27,7 @@ import org.sopt.and.R @Composable fun MyPageProfileSection( - deliveredEmail: String, + deliveredUserName: String, ){ Column( @@ -52,7 +52,7 @@ fun MyPageProfileSection( ) Spacer(modifier = Modifier.width(10.dp)) Text( - "${deliveredEmail}님", + "${deliveredUserName}님", color = Color.White ) Spacer(modifier = Modifier.weight(0.5f)) diff --git a/app/src/main/java/org/sopt/and/ui/components/SignUpandLogIn/SignUpTextField.kt b/app/src/main/java/org/sopt/and/ui/components/SignUpandLogIn/SignUpTextField.kt index 70bdb5e..ab62a3d 100644 --- a/app/src/main/java/org/sopt/and/ui/components/SignUpandLogIn/SignUpTextField.kt +++ b/app/src/main/java/org/sopt/and/ui/components/SignUpandLogIn/SignUpTextField.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.unit.sp fun SignUpTextField( modifier: Modifier = Modifier, onValueChange: (String) -> Unit, - fieldType: String, //Email 혹은 Password로 전달 예정 + fieldType: String, //Username 혹은 Password로 전달 예정 text: String, conditionCheck: Boolean, errMessage: String, diff --git a/gradle.properties b/gradle.properties index 20e2a01..d3a4885 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,5 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.defaults.buildfeatures.buildconfig=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7da403f..b33b409 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,9 @@ activityCompose = "1.8.0" composeBom = "2024.04.01" androidxComposeNavigation = "2.8.2" kotlinxSerializationJson = "1.7.3" +okhttp = "4.11.0" +retrofit = "2.9.0" +retrofitKotlinSerializationConverter = "1.0.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -27,6 +30,11 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation"} +okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } +okhttp = { group = "com.squareup.okhttp3", name = "okhttp" } +okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor" } +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-kotlin-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinSerializationConverter" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } [plugins] From 71d677fea11f3d5e4f4c3c6ff82f79b78c2baa05 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Sat, 16 Nov 2024 02:32:11 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[feat]=20=EC=9C=A0=EC=A0=80=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/MainActivity.kt | 3 +- .../and/model/dto/login/RequestGetUserDto.kt | 11 ++++ .../model/dto/login/ResponseGetUserFailDto.kt | 10 ++++ .../dto/login/ResponseGetUserSuccessDto.kt | 16 +++++ .../dto/login/ResponseGetUserWrapperDto.kt | 12 ++++ .../dto/mypage/RequestGetUserHobbyDto.kt | 11 ++++ .../dto/mypage/ResponseGetUserHobbyFailDto.kt | 10 ++++ .../mypage/ResponseGetUserHobbySuccessDto.kt | 16 +++++ .../mypage/ResponseGetUserHobbyWrapperDto.kt | 14 +++++ .../dto/{ => signup}/RequestCreateUserDto.kt | 2 +- .../{ => signup}/ResponseCreateUserFailDto.kt | 2 +- .../ResponseCreateUserSuccessDto.kt | 2 +- .../ResponseCreateUserWrapperDto.kt | 6 +- .../org/sopt/and/model/network/UserService.kt | 30 +++++++--- .../presentation/loginScreen/LoginScreen.kt | 23 ++++++-- .../loginScreen/LoginViewModel.kt | 59 ++++++++++++++++++- .../and/presentation/main/UserViewModel.kt | 11 +++- .../presentation/mypageScreen/MypageScreen.kt | 5 +- .../mypageScreen/MypageViewModel.kt | 41 +++++++++++++ .../presentation/signupScreen/SignUpScreen.kt | 9 +-- .../signupScreen/SignUpViewModel.kt | 15 ++--- .../MypageScreen/MyPageProfileSection.kt | 6 +- 22 files changed, 276 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt create mode 100644 app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt rename app/src/main/java/org/sopt/and/model/dto/{ => signup}/RequestCreateUserDto.kt (88%) rename app/src/main/java/org/sopt/and/model/dto/{ => signup}/ResponseCreateUserFailDto.kt (87%) rename app/src/main/java/org/sopt/and/model/dto/{ => signup}/ResponseCreateUserSuccessDto.kt (87%) rename app/src/main/java/org/sopt/and/model/dto/{ => signup}/ResponseCreateUserWrapperDto.kt (64%) diff --git a/app/src/main/java/org/sopt/and/MainActivity.kt b/app/src/main/java/org/sopt/and/MainActivity.kt index 2d8ce44..d713d6d 100644 --- a/app/src/main/java/org/sopt/and/MainActivity.kt +++ b/app/src/main/java/org/sopt/and/MainActivity.kt @@ -62,7 +62,8 @@ class MainActivity : ComponentActivity() { navigateToHomeScreen = { navController.navigate("home") }, - userViewModel = userViewModel + userViewModel = userViewModel, + mypageViewModel = mypageViewModel ) } diff --git a/app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt b/app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt new file mode 100644 index 0000000..2eeefb0 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt @@ -0,0 +1,11 @@ +package org.sopt.and.model.dto.login + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +//요청할 때는 username만 가지고 요청 (unique해야 함) +@Serializable +data class RequestGetUserDto( + @SerialName("username") + val userName: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt new file mode 100644 index 0000000..dad02e6 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt @@ -0,0 +1,10 @@ +package org.sopt.and.model.dto.login + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetUserFailDto( + @SerialName("code") + val no: Int +) diff --git a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt new file mode 100644 index 0000000..d66e68f --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt @@ -0,0 +1,16 @@ +package org.sopt.and.model.dto.login + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetUserSuccessDto( + @SerialName("result") + val result: UserResult +) + +@Serializable +data class UserResult( + @SerialName("token") + val token: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt new file mode 100644 index 0000000..f0080e3 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt @@ -0,0 +1,12 @@ +package org.sopt.and.model.dto.login + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetUserWrapperDto( + @SerialName("success") + val success: ResponseGetUserSuccessDto, + @SerialName("failed") + val failed: ResponseGetUserFailDto +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt b/app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt new file mode 100644 index 0000000..a386cfa --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt @@ -0,0 +1,11 @@ +package org.sopt.and.model.dto.mypage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +//요청할 때는 token만 가지고 요청 +@Serializable +data class RequestGetUserHobbyDto( + @SerialName("token") + val token: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt b/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt new file mode 100644 index 0000000..66edc96 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt @@ -0,0 +1,10 @@ +package org.sopt.and.model.dto.mypage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetUserHobbyFailDto( + @SerialName("code") + val no: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt b/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt new file mode 100644 index 0000000..4a7cead --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt @@ -0,0 +1,16 @@ +package org.sopt.and.model.dto.mypage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseGetUserHobbySuccessDto( + @SerialName("result") + val result: HobbyResult? = null, +) + +@Serializable +data class HobbyResult( + @SerialName("hobby") + val userHobby: String? = null, +) diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt b/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt new file mode 100644 index 0000000..d153d00 --- /dev/null +++ b/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt @@ -0,0 +1,14 @@ +package org.sopt.and.model.dto.mypage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.sopt.and.model.dto.login.ResponseGetUserFailDto +import org.sopt.and.model.dto.login.ResponseGetUserSuccessDto + +@Serializable +data class ResponseGetUserHobbyWrapperDto( + @SerialName("success") + val success: ResponseGetUserHobbySuccessDto, + @SerialName("failed") + val failed: ResponseGetUserHobbyFailDto +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/RequestCreateUserDto.kt similarity index 88% rename from app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt rename to app/src/main/java/org/sopt/and/model/dto/signup/RequestCreateUserDto.kt index 0074149..c9cb55c 100644 --- a/app/src/main/java/org/sopt/and/model/dto/RequestCreateUserDto.kt +++ b/app/src/main/java/org/sopt/and/model/dto/signup/RequestCreateUserDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto +package org.sopt.and.model.dto.signup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt similarity index 87% rename from app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt rename to app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt index 9b62f1d..2306440 100644 --- a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserFailDto.kt +++ b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto +package org.sopt.and.model.dto.signup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserSuccessDto.kt similarity index 87% rename from app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt rename to app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserSuccessDto.kt index e117dad..ec84658 100644 --- a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserSuccessDto.kt +++ b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserSuccessDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto +package org.sopt.and.model.dto.signup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt similarity index 64% rename from app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt rename to app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt index accd6ea..5439a6e 100644 --- a/app/src/main/java/org/sopt/and/model/dto/ResponseCreateUserWrapperDto.kt +++ b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto +package org.sopt.and.model.dto.signup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -6,9 +6,9 @@ import kotlinx.serialization.Serializable @Serializable data class ResponseCreateUserWrapperDto( //특정 유저 한 명의 응답 DTO @SerialName("success") - val data: ResponseCreateUserSuccessDto, + val success: ResponseCreateUserSuccessDto, @SerialName("failed") - val support: ResponseCreateUserFailDto + val failed: ResponseCreateUserFailDto ) diff --git a/app/src/main/java/org/sopt/and/model/network/UserService.kt b/app/src/main/java/org/sopt/and/model/network/UserService.kt index 3081c42..63b8293 100644 --- a/app/src/main/java/org/sopt/and/model/network/UserService.kt +++ b/app/src/main/java/org/sopt/and/model/network/UserService.kt @@ -1,23 +1,39 @@ package org.sopt.and.model.network -import org.sopt.and.model.dto.RequestCreateUserDto -import org.sopt.and.model.dto.ResponseCreateUserWrapperDto +import org.sopt.and.model.dto.login.RequestGetUserDto +import org.sopt.and.model.dto.login.ResponseGetUserFailDto +import org.sopt.and.model.dto.login.ResponseGetUserSuccessDto +import org.sopt.and.model.dto.login.ResponseGetUserWrapperDto +import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto +import org.sopt.and.model.dto.mypage.ResponseGetUserHobbyWrapperDto +import org.sopt.and.model.dto.signup.RequestCreateUserDto +import org.sopt.and.model.dto.signup.ResponseCreateUserSuccessDto +import org.sopt.and.model.dto.signup.ResponseCreateUserWrapperDto import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.POST -import retrofit2.http.Path -import retrofit2.http.Query //HTTP 메서드를 정의해둔 인터페이스 == service라고 부름. interface UserService { - //유저 회원가입 @POST("/user") fun signUpUser( @Body request: RequestCreateUserDto - ): Call - //여기서 Call : 응답이 왔을 때 불려질 타입 + ): Call //Call : 돌아오는 응답의 타입 + + @POST("/login") + fun logInUser( + @Body request: RequestGetUserDto + ): Call + + @GET("/user/my-hobby") + fun getMyHobby( + @Header("token") request: RequestGetUserHobbyDto + ): Call + + } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt index 61060d3..a56f1ce 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt @@ -35,7 +35,10 @@ import kotlinx.serialization.Serializable import org.sopt.and.presentation.signupScreen.StringInputValidCheck import org.sopt.and.presentation.signupScreen.PasswordValidCheck import org.sopt.and.R +import org.sopt.and.model.dto.login.RequestGetUserDto +import org.sopt.and.model.dto.signup.RequestCreateUserDto import org.sopt.and.presentation.main.UserViewModel +import org.sopt.and.presentation.mypageScreen.MypageViewModel import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection import org.sopt.and.ui.theme.ANDANDROIDTheme @@ -56,7 +59,9 @@ fun LoginScreen( passwordText: String, navigateToHomeScreen: () -> Unit, userViewModel: UserViewModel = viewModel(), - loginViewModel: LoginViewModel = viewModel() + mypageViewModel: MypageViewModel = viewModel(), + loginViewModel: LoginViewModel = remember { LoginViewModel(mypageViewModel) }, + ) { var userNameState = loginViewModel.userNameState.collectAsState().value @@ -118,7 +123,6 @@ fun LoginScreen( placeholder = "Wavve 비밀번호 설정", shouldShowPassword = shouldShowPassword, onPasswordVisibilityChange = { -// shouldShowPassword = !shouldShowPassword loginViewModel.togglePasswordVisibility() }, ) @@ -135,12 +139,23 @@ fun LoginScreen( loginMessage = "로그인 성공" loginSuccessFlag = 1 - /* TODO: 로그인 성공한 유저네임 갖고, 저장된 유저의 정보 가져오기. - * 로그인 성공 시, response로 돌아오는 token 값을 넘겨주기 */ + /* 로그인한 유저의 userName만 가지고 해당 유저의 정보 불러와야 함 */ + val loginedUser = RequestGetUserDto( + userName = userNameText, + ) + + //로그인 성공 시, token 값을 저장해 줌 + loginViewModel.logInUser(loginedUser, userViewModel) + /*TODO: 백엔드 연결 후 해당 코드 삭제*/ userViewModel.setUserName(userNameText) + + + + + } else { loginMessage = "알맞은 유저 이름과 비밀번호를 입력하세요" } diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt index be4da09..6ac2a40 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt @@ -1,12 +1,31 @@ package org.sopt.and.presentation.loginScreen +import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.sopt.and.model.dto.login.RequestGetUserDto +import org.sopt.and.model.dto.login.ResponseGetUserWrapperDto +import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto +import org.sopt.and.model.dto.signup.RequestCreateUserDto +import org.sopt.and.model.dto.signup.ResponseCreateUserWrapperDto +import org.sopt.and.model.network.ServicePool +import org.sopt.and.presentation.main.UserViewModel +import org.sopt.and.presentation.mypageScreen.MypageViewModel import org.sopt.and.presentation.signupScreen.StringInputValidCheck import org.sopt.and.presentation.signupScreen.PasswordValidCheck +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class LoginViewModel( + private val mypageViewModel: MypageViewModel +) : ViewModel() { + + //로그인 요청을 서버로 보내기 위함 + private val userService by lazy { ServicePool.userService } + -class LoginViewModel : ViewModel() { private val _userNameState = MutableStateFlow("") val userNameState: StateFlow = _userNameState @@ -44,4 +63,42 @@ class LoginViewModel : ViewModel() { fun isLoginValid(userNameText: String, passwordText: String): Boolean { return _userNameState.value == userNameText && _passwordState.value == passwordText } + + fun logInUser(request: RequestGetUserDto, userViewModel: UserViewModel) { + val TAG = "UserService" + + userService.logInUser(request).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val body = response.body() + if (body != null) { + val token = body.success.result.token + + Log.d(TAG, "유저 로그인 성공, 유저 토큰(id) : $token") + userViewModel.setLoginToken(token) + + val requestGetUserHobbyDto = RequestGetUserHobbyDto(token = token) + + val userHobby = mypageViewModel.getUserHobby(requestGetUserHobbyDto, userViewModel) + userViewModel.setHobby(userHobby.toString()) + + /*TODO: 이때의 token을 기반으로 user data 받아와 loginViewModel에 값 업데이트 해주기*/ + + + } else { + Log.e(TAG, "유저 로그인 실패") + } + } else { + Log.e(TAG, "유저 로그인 에러 ${response.code()}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "API 호출 도중 에러 발생: ${t.message}") + } + }) + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt b/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt index e4b9a33..a9eb535 100644 --- a/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/main/UserViewModel.kt @@ -15,12 +15,19 @@ class UserViewModel : ViewModel() { private val _hobby = MutableStateFlow("") val hobby: StateFlow = _hobby + private val _loginToken = MutableStateFlow("") + val loginToken: StateFlow = _loginToken + fun setUserName(newUserName: String) { _userName.value = newUserName } - fun setHobby(newHobbyString: String) { - _hobby.value = newHobbyString + fun setHobby(newHobby: String) { + _hobby.value = newHobby + } + + fun setLoginToken(newToken: String) { + _loginToken.value = newToken } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt index d71774f..165c184 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt @@ -35,7 +35,7 @@ fun MypageScreen( ) { var userNameText = userViewModel.userName.collectAsState().value - //val userName = userViewModel.userName + val userHobby = userViewModel.hobby.collectAsState().value Scaffold( bottomBar = { @@ -49,7 +49,8 @@ fun MypageScreen( .padding(innerPadding) ) { MyPageProfileSection( - deliveredUserName = userNameText + deliveredUserName = userNameText, + deliveredUserHobby = userHobby ) Spacer(modifier = Modifier.height(0.5.dp)) MyPageProfileSection2( diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt index 10e74ba..e8fae22 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -1,13 +1,24 @@ package org.sopt.and.presentation.mypageScreen +import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.sopt.and.model.dto.login.ResponseGetUserSuccessDto +import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto +import org.sopt.and.model.dto.mypage.ResponseGetUserHobbyWrapperDto +import org.sopt.and.model.network.ServicePool +import retrofit2.Callback import org.sopt.and.presentation.main.UserViewModel +import retrofit2.Call +import retrofit2.Response class MypageViewModel : ViewModel() { + //현재 로그인한 토큰으로 현 사용자의 취미를 불러오기 위함 + private val userService by lazy { ServicePool.userService } + private val _user = MutableStateFlow(UserViewModel()) val user: StateFlow = _user @@ -15,4 +26,34 @@ class MypageViewModel : ViewModel() { return _user.value.userName.toString() } + fun getUserHobby(request: RequestGetUserHobbyDto, userViewModel: UserViewModel) { + val TAG = "UserService" + + userService.getMyHobby(request).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + // 성공 응답 처리 + val body = response.body() + if (body != null) { + val userHobby = body.success.result?.userHobby ?: "default hobby" + Log.d("UserService", "유저 취미: $userHobby") + userViewModel.setHobby(userHobby) + } else { + Log.e("UserService", "응답 본문 없음") + } + } else { + // 실패 응답 처리 + } + } + + override fun onFailure(call: Call, t: Throwable) { + // 네트워크 오류 처리 + Log.e("UserService", "API 호출 실패: ${t.message}") + } + }) + + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt index f907a21..85f3fd6 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt @@ -1,6 +1,5 @@ package org.sopt.and.presentation.signupScreen -import android.util.Patterns import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -13,8 +12,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -24,11 +21,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.serialization.Serializable -import org.sopt.and.model.dto.RequestCreateUserDto +import org.sopt.and.model.dto.signup.RequestCreateUserDto +import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection import org.sopt.and.ui.theme.ANDANDROIDTheme -import kotlin.math.sign @Serializable data object SignUpScreen @@ -135,7 +132,6 @@ fun SignUpScreen( conditionCheck = isHobbyValid, errMessage = "취미은 7자 이하여야 합니다.", placeholder = "취미 입력", - //descriptionText = "로그인, 비밀번호 찾기, 알림에 사용되니 정확하게 입력해주세요.", ) Spacer(modifier = Modifier.weight(0.5f)) @@ -174,7 +170,6 @@ fun SignUpScreen( toastMessage = "회원가입에 성공하였습니다." - navigateToLoginScreen(userNameText, passwordText) } diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt index 8092e77..c682ec3 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt @@ -4,8 +4,8 @@ import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.and.model.dto.RequestCreateUserDto -import org.sopt.and.model.dto.ResponseCreateUserWrapperDto +import org.sopt.and.model.dto.signup.RequestCreateUserDto +import org.sopt.and.model.dto.signup.ResponseCreateUserWrapperDto import org.sopt.and.model.network.ServicePool import retrofit2.Call import retrofit2.Callback @@ -68,7 +68,8 @@ class SignUpViewModel : ViewModel() { fun createNewUser(request: RequestCreateUserDto) { val TAG = "UserService" - ServicePool.userService.signUpUser(request).enqueue(object : Callback { + userService.signUpUser(request).enqueue(object : Callback { + override fun onResponse( call: Call, response: Response @@ -76,17 +77,17 @@ class SignUpViewModel : ViewModel() { if (response.isSuccessful) { val body = response.body() if (body != null) { - Log.d(TAG, "User successfully created with no: ${body.data.result.no}") + Log.d(TAG, "유저 생성 성공 ${body.success.result.no}") } else { - Log.e(TAG, "Response body is null") + Log.e(TAG, "유저 생성 에러") } } else { - Log.e(TAG, "Failed with error code: ${response.code()}") + Log.e(TAG, "유저 생성 에러 ${response.code()}") } } override fun onFailure(call: Call, t: Throwable) { - Log.e(TAG, "Error during API call: ${t.message}") + Log.e(TAG, "API 호출 도중 에러 발생: ${t.message}") } }) } diff --git a/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt b/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt index a1911f9..be2dc2e 100644 --- a/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt +++ b/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt @@ -28,8 +28,12 @@ import org.sopt.and.R @Composable fun MyPageProfileSection( deliveredUserName: String, + deliveredUserHobby: String?, ){ + //임시 값 삽입 + val hobby = deliveredUserHobby ?: "eating" + Column( modifier = Modifier .fillMaxWidth() @@ -52,7 +56,7 @@ fun MyPageProfileSection( ) Spacer(modifier = Modifier.width(10.dp)) Text( - "${deliveredUserName}님", + "요즘 ${hobby}를 즐기는\n${deliveredUserName}님", color = Color.White ) Spacer(modifier = Modifier.weight(0.5f)) From ee9d13120e36c0214b0d27b90dbe39b8078c0337 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Tue, 3 Dec 2024 18:27:21 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feature=20#week4=20:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=ED=86=B5=EC=8B=A0=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/MainActivity.kt | 5 +- app/src/main/java/org/sopt/and/domain/User.kt | 7 ++ ...SuccessDto.kt => ResponseCreateUserDto.kt} | 21 +++-- .../dto/signup/ResponseCreateUserFailDto.kt | 16 ---- .../signup/ResponseCreateUserWrapperDto.kt | 14 ---- .../org/sopt/and/model/network/ApiFactory.kt | 2 +- .../org/sopt/and/model/network/UserService.kt | 8 +- .../loginScreen/LoginViewModel.kt | 1 - .../presentation/signupScreen/SignUpScreen.kt | 71 +++++++++-------- .../signupScreen/SignUpViewModel.kt | 76 ++++++++++--------- gradle.properties | 1 - 11 files changed, 107 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/domain/User.kt rename app/src/main/java/org/sopt/and/model/dto/signup/{ResponseCreateUserSuccessDto.kt => ResponseCreateUserDto.kt} (50%) delete mode 100644 app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt delete mode 100644 app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt diff --git a/app/src/main/java/org/sopt/and/MainActivity.kt b/app/src/main/java/org/sopt/and/MainActivity.kt index d713d6d..62328bc 100644 --- a/app/src/main/java/org/sopt/and/MainActivity.kt +++ b/app/src/main/java/org/sopt/and/MainActivity.kt @@ -17,6 +17,7 @@ import androidx.navigation.compose.rememberNavController import org.sopt.and.ui.theme.ANDANDROIDTheme import androidx.navigation.compose.composable import androidx.navigation.toRoute +import org.sopt.and.domain.User import org.sopt.and.presentation.loginScreen.LoginScreen import org.sopt.and.presentation.mypageScreen.MypageScreen import org.sopt.and.presentation.searchScreen.SearchScreen @@ -44,8 +45,8 @@ class MainActivity : ComponentActivity() { ){ composable { SignUpScreen( - navigateToLoginScreen = { userNameText, passwordText -> - navController.navigate(LoginScreen(userNameText, passwordText)) + navigateToLoginScreen = { user -> + User(user.name, user.password) } ) } diff --git a/app/src/main/java/org/sopt/and/domain/User.kt b/app/src/main/java/org/sopt/and/domain/User.kt new file mode 100644 index 0000000..623e220 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/User.kt @@ -0,0 +1,7 @@ +package org.sopt.and.domain + +data class User( + var name: String = "", + var password: String = "", + var hobby: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserSuccessDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserDto.kt similarity index 50% rename from app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserSuccessDto.kt rename to app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserDto.kt index ec84658..a41a2f8 100644 --- a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserSuccessDto.kt +++ b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserDto.kt @@ -4,13 +4,20 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ResponseCreateUserSuccessDto( - @SerialName("success") - val result: UserResult +data class ResponseCreateUserFailedDto( + @SerialName("code") + val code: String ) @Serializable -data class UserResult( - @SerialName("no") - val no: Int -) \ No newline at end of file +data class ResponseCreateUserSuccessDto( + @SerialName("result") + val result: UserResult +){ + @Serializable + data class UserResult( + @SerialName("no") + val no: Int + ) +} + diff --git a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt deleted file mode 100644 index 2306440..0000000 --- a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserFailDto.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.sopt.and.model.dto.signup - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseCreateUserFailDto( - @SerialName("failed") - val code: FailureResult -) - -@Serializable -data class FailureResult( - @SerialName("code") - val no: Int -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt b/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt deleted file mode 100644 index 5439a6e..0000000 --- a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserWrapperDto.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.sopt.and.model.dto.signup - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseCreateUserWrapperDto( //특정 유저 한 명의 응답 DTO - @SerialName("success") - val success: ResponseCreateUserSuccessDto, - @SerialName("failed") - val failed: ResponseCreateUserFailDto -) - - diff --git a/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt b/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt index 536e8aa..69b993e 100644 --- a/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt +++ b/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt @@ -9,7 +9,7 @@ import org.sopt.and.BuildConfig import retrofit2.Retrofit object ApiFactory { - private const val BASE_URL: String = BuildConfig.BASE_URL + private const val BASE_URL: String = "http://223.130.135.50:8085/" private val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY diff --git a/app/src/main/java/org/sopt/and/model/network/UserService.kt b/app/src/main/java/org/sopt/and/model/network/UserService.kt index 63b8293..22c81f6 100644 --- a/app/src/main/java/org/sopt/and/model/network/UserService.kt +++ b/app/src/main/java/org/sopt/and/model/network/UserService.kt @@ -8,8 +8,8 @@ import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto import org.sopt.and.model.dto.mypage.ResponseGetUserHobbyWrapperDto import org.sopt.and.model.dto.signup.RequestCreateUserDto import org.sopt.and.model.dto.signup.ResponseCreateUserSuccessDto -import org.sopt.and.model.dto.signup.ResponseCreateUserWrapperDto import retrofit2.Call +import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -20,9 +20,9 @@ import retrofit2.http.POST interface UserService { @POST("/user") - fun signUpUser( - @Body request: RequestCreateUserDto - ): Call //Call : 돌아오는 응답의 타입 + suspend fun signUpUser( + @Body requestDto: RequestCreateUserDto + ): Response @POST("/login") fun logInUser( diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt index 6ac2a40..e10b13c 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt @@ -8,7 +8,6 @@ import org.sopt.and.model.dto.login.RequestGetUserDto import org.sopt.and.model.dto.login.ResponseGetUserWrapperDto import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto import org.sopt.and.model.dto.signup.RequestCreateUserDto -import org.sopt.and.model.dto.signup.ResponseCreateUserWrapperDto import org.sopt.and.model.network.ServicePool import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.presentation.mypageScreen.MypageViewModel diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt index 85f3fd6..fc7152b 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt @@ -12,6 +12,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -20,12 +23,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import org.sopt.and.domain.User import org.sopt.and.model.dto.signup.RequestCreateUserDto import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection import org.sopt.and.ui.theme.ANDANDROIDTheme +import kotlin.math.sign @Serializable data object SignUpScreen @@ -42,29 +48,32 @@ fun StringInputValidCheck(newString: String): Boolean { } fun PasswordValidCheck(password: String): Boolean { - val pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#\$%^&*])|(?=.*[a-z])(?=.*\\d)(?=.*[!@#\$%^&*])|(?=.*[A-Z])(?=.*\\d)(?=.*[!@#\$%^&*]).{8,20}\$".toRegex() + val pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#\$%^&*])|(?=.*[a-z])(?=.*\\d)(?=.*[!@#\$%^&*])|(?=.*[A-Z])(?=.*\\d)(?=.*[!@#\$%^&*]).{1,8}\$".toRegex() return password.matches(pattern) - } + @Composable fun SignUpScreen( modifier: Modifier = Modifier, - navigateToLoginScreen: (userNameText: String, passwordText: String) -> Unit, + navigateToLoginScreen: (user: User) -> Unit = {}, signUpViewModel: SignUpViewModel = viewModel() ) { val context = LocalContext.current var toastMessage = "" - var userNameText = signUpViewModel.userNameText.collectAsState().value - var passwordText = signUpViewModel.passwordText.collectAsState().value - var hobbyText = signUpViewModel.hobbyText.collectAsState().value + var userNameText = remember { mutableStateOf("") } + var passwordText = remember { mutableStateOf("") } + var hobbyText = remember { mutableStateOf("") } + var isUserNameValid = signUpViewModel.isUserNameValid.collectAsState().value var isPasswordValid = signUpViewModel.isPasswordValid.collectAsState().value var isHobbyValid = signUpViewModel.isHobbyValid.collectAsState().value var shouldShowPassword = signUpViewModel.shouldShowPassword.collectAsState().value + val coroutineScope = rememberCoroutineScope() + Column( modifier = Modifier .fillMaxSize() @@ -91,10 +100,11 @@ fun SignUpScreen( Spacer(modifier = Modifier.weight(0.25f)) SignUpTextField( - text = userNameText, - onValueChange = { newValue -> - signUpViewModel.onUserNameChange(newValue) - isUserNameValid = StringInputValidCheck(userNameText) + text = userNameText.value, + onValueChange = { + userNameText.value = it + signUpViewModel.onUserNameChange(it) + isUserNameValid = StringInputValidCheck(it) }, fieldType = "userName", conditionCheck = isUserNameValid, @@ -106,10 +116,11 @@ fun SignUpScreen( Spacer(modifier = Modifier.weight(0.15f)) SignUpTextField( - text = passwordText, - onValueChange = { newValue -> - signUpViewModel.onPasswordChange(newValue) - isPasswordValid = PasswordValidCheck(passwordText) + text = passwordText.value, + onValueChange = { + passwordText.value = it + signUpViewModel.onPasswordChange(it) /*todo: onpasswordchange 안에 isvalid 체킹하는 로직을 넣기.*/ + isPasswordValid = PasswordValidCheck(it) }, fieldType = "Password", conditionCheck = isPasswordValid, @@ -123,10 +134,11 @@ fun SignUpScreen( ) SignUpTextField( - text = hobbyText, - onValueChange = { newValue -> - signUpViewModel.onHobbyChange(newValue) - isHobbyValid = StringInputValidCheck(hobbyText) + text = hobbyText.value, + onValueChange = { + hobbyText.value = it + signUpViewModel.onHobbyChange(it) + isHobbyValid = StringInputValidCheck(it) }, fieldType = "hobby", conditionCheck = isHobbyValid, @@ -147,30 +159,27 @@ fun SignUpScreen( .padding(vertical = 13.dp) .clickable { + /*Todo: 클릭 시 조건 검사 및 토스트 띄우는 것까지 createNewUser 안에 넣기*/ + //유저 네임 형식 조건 검사 - if (!StringInputValidCheck(userNameText)) { + if (!StringInputValidCheck(userNameText.value)) { toastMessage = "형식에 맞는 유저 네임을 입력하세요" } - //비밀번호 형식 조건 검사 - if (!PasswordValidCheck(passwordText)) { + if (!PasswordValidCheck(passwordText.value)) { toastMessage = "조건에 맞는 비밀번호를 사용하세요" } if (signUpViewModel.isSignUpValid()) { - /* 새로 회원가입한 정보 저장 */ - val newUser = RequestCreateUserDto( - userName = userNameText, - password = passwordText, - hobby = hobbyText - ) - signUpViewModel.createNewUser(newUser) + coroutineScope.launch { + signUpViewModel.createNewUser() + } toastMessage = "회원가입에 성공하였습니다." - navigateToLoginScreen(userNameText, passwordText) + navigateToLoginScreen(User(userNameText.value, passwordText.value)) } Toast @@ -190,10 +199,6 @@ fun SignUpScreen( fun SignUpPreview() { ANDANDROIDTheme { SignUpScreen( - navigateToLoginScreen = { - userName, password -> - println("userName: $userName, password: $password") - } ) } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt index c682ec3..23cd8bc 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt @@ -4,8 +4,11 @@ import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.json.Json +import org.sopt.and.domain.User import org.sopt.and.model.dto.signup.RequestCreateUserDto -import org.sopt.and.model.dto.signup.ResponseCreateUserWrapperDto +import org.sopt.and.model.dto.signup.ResponseCreateUserFailedDto +import org.sopt.and.model.dto.signup.ResponseCreateUserSuccessDto import org.sopt.and.model.network.ServicePool import retrofit2.Call import retrofit2.Callback @@ -16,14 +19,15 @@ class SignUpViewModel : ViewModel() { //회원가입 성공 시 서버로 create 요청 보내기 위함 private val userService by lazy { ServicePool.userService } - private val _userNameText = MutableStateFlow("") - val userNameText: StateFlow = _userNameText + private val _user = MutableStateFlow(User()) + val user: StateFlow = _user - private val _passwordText = MutableStateFlow("") - val passwordText: StateFlow = _passwordText + private val _signUpResult = MutableStateFlow?>(null) + val signUpResult: StateFlow?> = _signUpResult - private val _hobbyText = MutableStateFlow("") - val hobbyText: StateFlow = _hobbyText +// val userNameText = _user.value.name +// val userPasswordText = _user.value.password +// val userHobbyText = _user.value.hobby private val _isUserNameValid = MutableStateFlow(true) val isUserNameValid: StateFlow = _isUserNameValid @@ -40,19 +44,19 @@ class SignUpViewModel : ViewModel() { // 유저 네임 입력 시 입력한 값 보이기 fun onUserNameChange(newUserName: String) { - _userNameText.value = newUserName + _user.value = _user.value.copy(name = newUserName) _isUserNameValid.value = StringInputValidCheck(newUserName) } // 비밀번호 입력 시 입력한 값 보이기 fun onPasswordChange(newPassword: String) { - _passwordText.value = newPassword + _user.value = _user.value.copy(password = newPassword) _isPasswordValid.value = PasswordValidCheck(newPassword) } - // 비밀번호 입력 시 입력한 값 보이기 + // 취미 입력 시 입력한 값 보이기 fun onHobbyChange(newHobby: String) { - _hobbyText.value = newHobby + _user.value = _user.value.copy(hobby = newHobby) _isHobbyValid.value = StringInputValidCheck(newHobby) } @@ -65,34 +69,34 @@ class SignUpViewModel : ViewModel() { return _isUserNameValid.value && _isPasswordValid.value } - fun createNewUser(request: RequestCreateUserDto) { - val TAG = "UserService" - - userService.signUpUser(request).enqueue(object : Callback { - - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - val body = response.body() - if (body != null) { - Log.d(TAG, "유저 생성 성공 ${body.success.result.no}") - } else { - Log.e(TAG, "유저 생성 에러") - } + suspend fun createNewUser() { + val requestDto = RequestCreateUserDto( + userName = _user.value.name, + password = _user.value.password, + hobby = _user.value.hobby + ) + + try { + val response = userService.signUpUser(requestDto) + if(response.isSuccessful) { + _signUpResult.value = Result.success(Unit) + Log.d("로그인 성공", "Status code: ${response.code()}") + } else { + val errorBody = response.errorBody()?.string() + Log.e("서버 응답", "Raw response body: $errorBody") + val errorCode = if (errorBody != null){ + val errorData = Json.decodeFromString(errorBody) + errorData.code } else { - Log.e(TAG, "유저 생성 에러 ${response.code()}") + "Unknown error code" } - } - override fun onFailure(call: Call, t: Throwable) { - Log.e(TAG, "API 호출 도중 에러 발생: ${t.message}") + _signUpResult.value = Result.failure(Exception("Status code is ${response.code()} and error code is $errorCode")) } - }) - } - - - + } catch (e: Exception) { + Log.e("Login error", "Exception: ${e.message}") + _signUpResult.value = Result.failure(e) + } + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index d3a4885..132244e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,4 +21,3 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.defaults.buildfeatures.buildconfig=true From 841d04ed030cb3e9bbe4fa9274a623d4cf759b5b Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Tue, 3 Dec 2024 18:51:44 +0900 Subject: [PATCH 05/11] =?UTF-8?q?fix=20#week4=20:=20signup=20=ED=9B=84=20l?= =?UTF-8?q?ogin=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20navigate=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/org/sopt/and/MainActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/sopt/and/MainActivity.kt b/app/src/main/java/org/sopt/and/MainActivity.kt index 62328bc..60ae722 100644 --- a/app/src/main/java/org/sopt/and/MainActivity.kt +++ b/app/src/main/java/org/sopt/and/MainActivity.kt @@ -46,7 +46,10 @@ class MainActivity : ComponentActivity() { composable { SignUpScreen( navigateToLoginScreen = { user -> - User(user.name, user.password) + navController.navigate(LoginScreen(user.name, user.password)){ + popUpTo(SignUpScreen) { inclusive = true } + launchSingleTop = true + } } ) } From 72b791b35463c1fdfff20e5865406372edf08b39 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Tue, 3 Dec 2024 22:19:37 +0900 Subject: [PATCH 06/11] =?UTF-8?q?fix=20#week4=20:=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=ED=8F=B4=EB=8D=94=EB=A7=81=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/MainActivity.kt | 77 +++++++++------ .../datasource/UserInfoLocalDataSource.kt | 7 ++ .../UserInfoLocalDataSourceImpl.kt | 29 ++++++ .../dto/login/RequestGetUserDto.kt | 5 +- .../dto/login/ResponseGetUserDto.kt} | 10 +- .../dto/mypage/RequestGetUserHobbyDto.kt | 2 +- .../dto/mypage/ResponseGetUserHobbyFailDto.kt | 2 +- .../mypage/ResponseGetUserHobbySuccessDto.kt | 2 +- .../mypage/ResponseGetUserHobbyWrapperDto.kt | 4 +- .../dto/signup/RequestCreateUserDto.kt | 2 +- .../dto/signup/ResponseCreateUserDto.kt | 2 +- .../and/{model => data}/network/ApiFactory.kt | 3 +- .../{model => data}/network/UserService.kt | 20 ++-- .../model/dto/login/ResponseGetUserFailDto.kt | 10 -- .../dto/login/ResponseGetUserWrapperDto.kt | 12 --- .../presentation/loginScreen/LoginScreen.kt | 95 ++++++++---------- .../loginScreen/LoginViewModel.kt | 97 ++++++++++--------- .../mypageScreen/MypageViewModel.kt | 7 +- .../presentation/signupScreen/SignUpScreen.kt | 2 +- .../signupScreen/SignUpViewModel.kt | 11 +-- .../BottomBar/CustomBottomAppBar.kt | 1 - .../and/ui/components/BottomBar/NavIcon.kt | 20 +++- .../java/org/sopt/and/util/Constraints.kt | 26 +++++ 23 files changed, 257 insertions(+), 189 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/data/datalocal/datasource/UserInfoLocalDataSource.kt create mode 100644 app/src/main/java/org/sopt/and/data/datalocal/datasourceimpl/UserInfoLocalDataSourceImpl.kt rename app/src/main/java/org/sopt/and/{model => data}/dto/login/RequestGetUserDto.kt (63%) rename app/src/main/java/org/sopt/and/{model/dto/login/ResponseGetUserSuccessDto.kt => data/dto/login/ResponseGetUserDto.kt} (59%) rename app/src/main/java/org/sopt/and/{model => data}/dto/mypage/RequestGetUserHobbyDto.kt (85%) rename app/src/main/java/org/sopt/and/{model => data}/dto/mypage/ResponseGetUserHobbyFailDto.kt (82%) rename app/src/main/java/org/sopt/and/{model => data}/dto/mypage/ResponseGetUserHobbySuccessDto.kt (88%) rename app/src/main/java/org/sopt/and/{model => data}/dto/mypage/ResponseGetUserHobbyWrapperDto.kt (64%) rename app/src/main/java/org/sopt/and/{model => data}/dto/signup/RequestCreateUserDto.kt (88%) rename app/src/main/java/org/sopt/and/{model => data}/dto/signup/ResponseCreateUserDto.kt (91%) rename app/src/main/java/org/sopt/and/{model => data}/network/ApiFactory.kt (94%) rename app/src/main/java/org/sopt/and/{model => data}/network/UserService.kt (52%) delete mode 100644 app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt delete mode 100644 app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt create mode 100644 app/src/main/java/org/sopt/and/util/Constraints.kt diff --git a/app/src/main/java/org/sopt/and/MainActivity.kt b/app/src/main/java/org/sopt/and/MainActivity.kt index 60ae722..f85c040 100644 --- a/app/src/main/java/org/sopt/and/MainActivity.kt +++ b/app/src/main/java/org/sopt/and/MainActivity.kt @@ -17,15 +17,16 @@ import androidx.navigation.compose.rememberNavController import org.sopt.and.ui.theme.ANDANDROIDTheme import androidx.navigation.compose.composable import androidx.navigation.toRoute -import org.sopt.and.domain.User +import org.sopt.and.data.datalocal.datasourceimpl.UserInfoLocalDataSourceImpl import org.sopt.and.presentation.loginScreen.LoginScreen import org.sopt.and.presentation.mypageScreen.MypageScreen import org.sopt.and.presentation.searchScreen.SearchScreen import org.sopt.and.presentation.signupScreen.SignUpScreen import org.sopt.and.presentation.homeScreen.HomeScreen import org.sopt.and.presentation.homeScreen.HomeViewModel -import org.sopt.and.presentation.main.UserViewModel +import org.sopt.and.presentation.loginScreen.LoginViewModel import org.sopt.and.presentation.mypageScreen.MypageViewModel +import org.sopt.and.util.Route class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -35,64 +36,84 @@ class MainActivity : ComponentActivity() { ANDANDROIDTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> val navController = rememberNavController() - val userViewModel: UserViewModel = viewModel() - val mypageViewModel : MypageViewModel = viewModel() + + val context = navController.context + val userInfoLocalDataSource = UserInfoLocalDataSourceImpl(context) NavHost( navController = navController, - startDestination = SignUpScreen, + startDestination = Route.SignUpScreen(userName = "", password = ""), modifier = Modifier.padding(innerPadding) ){ - composable { + composable { backStackEntry -> + val item = backStackEntry.toRoute() SignUpScreen( - navigateToLoginScreen = { user -> - navController.navigate(LoginScreen(user.name, user.password)){ - popUpTo(SignUpScreen) { inclusive = true } + navigateToLoginScreen = { + navController.navigate(Route.LoginScreen){ + popUpTo { inclusive = true } launchSingleTop = true } } ) } - composable { backStackEntry -> - val item = backStackEntry.toRoute() - val scope = rememberCoroutineScope() - val snackbarHostState = remember { SnackbarHostState() } + composable { backStackEntry -> + val item = backStackEntry.toRoute() LoginScreen( - userNameText = item.userNameText, - passwordText = item.passwordText, - scope = scope, - snackbarHostState = snackbarHostState, + loginViewModel = LoginViewModel(userInfoLocalDataSource = userInfoLocalDataSource), navigateToHomeScreen = { - navController.navigate("home") + navController.navigate(Route.HomeScreen){ + popUpTo { inclusive = true} + launchSingleTop = true + } }, - userViewModel = userViewModel, - mypageViewModel = mypageViewModel ) } - composable("home") { + composable { backStackEntry -> + val item = backStackEntry.toRoute() HomeScreen( + homeViewModel = HomeViewModel(), navController = navController, - homeViewModel = HomeViewModel() ) } - composable("search") { + composable { backStackEntry -> + val item = backStackEntry.toRoute() SearchScreen( navController = navController ) } - composable("profile") { - + composable { backStackEntry -> + val item = backStackEntry.toRoute() MypageScreen( - navController = navController, - userViewModel = userViewModel, - mypageViewModel = mypageViewModel + mypageViewModel = MypageViewModel(), + navController = navController ) } +// composable("home") { +// HomeScreen( +// navController = navController, +// homeViewModel = HomeViewModel() +// ) +// } +// +// composable("search") { +// SearchScreen( +// navController = navController +// ) +// } + +// composable("profile") { +// +// MypageScreen( +// navController = navController, +// mypageViewModel = mypageViewModel +// ) +// } + } } } diff --git a/app/src/main/java/org/sopt/and/data/datalocal/datasource/UserInfoLocalDataSource.kt b/app/src/main/java/org/sopt/and/data/datalocal/datasource/UserInfoLocalDataSource.kt new file mode 100644 index 0000000..c3d939b --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datalocal/datasource/UserInfoLocalDataSource.kt @@ -0,0 +1,7 @@ +package org.sopt.and.data.datalocal.datasource + +interface UserInfoLocalDataSource { + var accessToken: String + var userName: String + fun clear() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/datalocal/datasourceimpl/UserInfoLocalDataSourceImpl.kt b/app/src/main/java/org/sopt/and/data/datalocal/datasourceimpl/UserInfoLocalDataSourceImpl.kt new file mode 100644 index 0000000..f4124a9 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datalocal/datasourceimpl/UserInfoLocalDataSourceImpl.kt @@ -0,0 +1,29 @@ +package org.sopt.and.data.datalocal.datasourceimpl + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import org.sopt.and.data.datalocal.datasource.UserInfoLocalDataSource + +class UserInfoLocalDataSourceImpl(context: Context) : UserInfoLocalDataSource { + + private val sharedPreferences: SharedPreferences = + context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + + override var accessToken: String + get() = sharedPreferences.getString(ACCESSTOKEN, INITIAL_VALUE).toString() + set(value) = sharedPreferences.edit { putString(ACCESSTOKEN, value)} + + override var userName: String + get() = sharedPreferences.getString(USERNAME, INITIAL_VALUE).toString() + set(value) = sharedPreferences.edit { putString(USERNAME, value) } + + override fun clear() = sharedPreferences.edit { clear() } + + companion object { + const val PREFERENCES_NAME = "user_preferences" + const val ACCESSTOKEN = "accesstoken" + const val USERNAME = "userName" + const val INITIAL_VALUE = "" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt b/app/src/main/java/org/sopt/and/data/dto/login/RequestGetUserDto.kt similarity index 63% rename from app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt rename to app/src/main/java/org/sopt/and/data/dto/login/RequestGetUserDto.kt index 2eeefb0..6af7ed9 100644 --- a/app/src/main/java/org/sopt/and/model/dto/login/RequestGetUserDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/login/RequestGetUserDto.kt @@ -1,11 +1,12 @@ -package org.sopt.and.model.dto.login +package org.sopt.and.data.dto.login import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -//요청할 때는 username만 가지고 요청 (unique해야 함) @Serializable data class RequestGetUserDto( @SerialName("username") val userName: String, + @SerialName("password") + val password: String ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt b/app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt similarity index 59% rename from app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt rename to app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt index d66e68f..a446493 100644 --- a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserSuccessDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt @@ -1,10 +1,16 @@ -package org.sopt.and.model.dto.login +package org.sopt.and.data.dto.login import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ResponseGetUserSuccessDto( +data class ResponseGetUserFailedDto( + @SerialName("code") + val code: String +) + +@Serializable +data class ResponseGetUserDto( @SerialName("result") val result: UserResult ) diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt similarity index 85% rename from app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt rename to app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt index a386cfa..af0a098 100644 --- a/app/src/main/java/org/sopt/and/model/dto/mypage/RequestGetUserHobbyDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto.mypage +package org.sopt.and.data.dto.mypage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt similarity index 82% rename from app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt rename to app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt index 66edc96..ec1bbba 100644 --- a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyFailDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto.mypage +package org.sopt.and.data.dto.mypage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt similarity index 88% rename from app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt rename to app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt index 4a7cead..ace3010 100644 --- a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbySuccessDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto.mypage +package org.sopt.and.data.dto.mypage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt similarity index 64% rename from app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt rename to app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt index d153d00..b286830 100644 --- a/app/src/main/java/org/sopt/and/model/dto/mypage/ResponseGetUserHobbyWrapperDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt @@ -1,9 +1,7 @@ -package org.sopt.and.model.dto.mypage +package org.sopt.and.data.dto.mypage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import org.sopt.and.model.dto.login.ResponseGetUserFailDto -import org.sopt.and.model.dto.login.ResponseGetUserSuccessDto @Serializable data class ResponseGetUserHobbyWrapperDto( diff --git a/app/src/main/java/org/sopt/and/model/dto/signup/RequestCreateUserDto.kt b/app/src/main/java/org/sopt/and/data/dto/signup/RequestCreateUserDto.kt similarity index 88% rename from app/src/main/java/org/sopt/and/model/dto/signup/RequestCreateUserDto.kt rename to app/src/main/java/org/sopt/and/data/dto/signup/RequestCreateUserDto.kt index c9cb55c..cff38ae 100644 --- a/app/src/main/java/org/sopt/and/model/dto/signup/RequestCreateUserDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/signup/RequestCreateUserDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto.signup +package org.sopt.and.data.dto.signup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserDto.kt b/app/src/main/java/org/sopt/and/data/dto/signup/ResponseCreateUserDto.kt similarity index 91% rename from app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserDto.kt rename to app/src/main/java/org/sopt/and/data/dto/signup/ResponseCreateUserDto.kt index a41a2f8..69b7301 100644 --- a/app/src/main/java/org/sopt/and/model/dto/signup/ResponseCreateUserDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/signup/ResponseCreateUserDto.kt @@ -1,4 +1,4 @@ -package org.sopt.and.model.dto.signup +package org.sopt.and.data.dto.signup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt b/app/src/main/java/org/sopt/and/data/network/ApiFactory.kt similarity index 94% rename from app/src/main/java/org/sopt/and/model/network/ApiFactory.kt rename to app/src/main/java/org/sopt/and/data/network/ApiFactory.kt index 69b993e..5efe441 100644 --- a/app/src/main/java/org/sopt/and/model/network/ApiFactory.kt +++ b/app/src/main/java/org/sopt/and/data/network/ApiFactory.kt @@ -1,11 +1,10 @@ -package org.sopt.and.model.network +package org.sopt.and.data.network import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import org.sopt.and.BuildConfig import retrofit2.Retrofit object ApiFactory { diff --git a/app/src/main/java/org/sopt/and/model/network/UserService.kt b/app/src/main/java/org/sopt/and/data/network/UserService.kt similarity index 52% rename from app/src/main/java/org/sopt/and/model/network/UserService.kt rename to app/src/main/java/org/sopt/and/data/network/UserService.kt index 22c81f6..c82c33c 100644 --- a/app/src/main/java/org/sopt/and/model/network/UserService.kt +++ b/app/src/main/java/org/sopt/and/data/network/UserService.kt @@ -1,13 +1,11 @@ -package org.sopt.and.model.network - -import org.sopt.and.model.dto.login.RequestGetUserDto -import org.sopt.and.model.dto.login.ResponseGetUserFailDto -import org.sopt.and.model.dto.login.ResponseGetUserSuccessDto -import org.sopt.and.model.dto.login.ResponseGetUserWrapperDto -import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto -import org.sopt.and.model.dto.mypage.ResponseGetUserHobbyWrapperDto -import org.sopt.and.model.dto.signup.RequestCreateUserDto -import org.sopt.and.model.dto.signup.ResponseCreateUserSuccessDto +package org.sopt.and.data.network + +import org.sopt.and.data.dto.login.RequestGetUserDto +import org.sopt.and.data.dto.login.ResponseGetUserDto +import org.sopt.and.data.dto.mypage.RequestGetUserHobbyDto +import org.sopt.and.data.dto.mypage.ResponseGetUserHobbyWrapperDto +import org.sopt.and.data.dto.signup.RequestCreateUserDto +import org.sopt.and.data.dto.signup.ResponseCreateUserSuccessDto import retrofit2.Call import retrofit2.Response import retrofit2.http.Body @@ -27,7 +25,7 @@ interface UserService { @POST("/login") fun logInUser( @Body request: RequestGetUserDto - ): Call + ): Response @GET("/user/my-hobby") fun getMyHobby( diff --git a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt deleted file mode 100644 index dad02e6..0000000 --- a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserFailDto.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.and.model.dto.login - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseGetUserFailDto( - @SerialName("code") - val no: Int -) diff --git a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt b/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt deleted file mode 100644 index f0080e3..0000000 --- a/app/src/main/java/org/sopt/and/model/dto/login/ResponseGetUserWrapperDto.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.sopt.and.model.dto.login - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseGetUserWrapperDto( - @SerialName("success") - val success: ResponseGetUserSuccessDto, - @SerialName("failed") - val failed: ResponseGetUserFailDto -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt index a56f1ce..38fe6bf 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -35,8 +36,8 @@ import kotlinx.serialization.Serializable import org.sopt.and.presentation.signupScreen.StringInputValidCheck import org.sopt.and.presentation.signupScreen.PasswordValidCheck import org.sopt.and.R -import org.sopt.and.model.dto.login.RequestGetUserDto -import org.sopt.and.model.dto.signup.RequestCreateUserDto +import org.sopt.and.data.dto.login.RequestGetUserDto +import org.sopt.and.data.dto.signup.RequestCreateUserDto import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.presentation.mypageScreen.MypageViewModel import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField @@ -53,27 +54,25 @@ data class LoginScreen( @Composable fun LoginScreen( modifier: Modifier = Modifier.fillMaxSize(), - scope: CoroutineScope, - snackbarHostState: SnackbarHostState, - userNameText: String, - passwordText: String, navigateToHomeScreen: () -> Unit, - userViewModel: UserViewModel = viewModel(), - mypageViewModel: MypageViewModel = viewModel(), - loginViewModel: LoginViewModel = remember { LoginViewModel(mypageViewModel) }, - + loginViewModel: LoginViewModel, ) { - var userNameState = loginViewModel.userNameState.collectAsState().value - var passwordState = loginViewModel.passwordState.collectAsState().value + var userNameText = remember { mutableStateOf("") } + var passwordText = remember { mutableStateOf("") } + var isUserNameValid = loginViewModel.isUserNameValid.collectAsState().value var isPasswordValid = loginViewModel.isPasswordValid.collectAsState().value var shouldShowPassword = loginViewModel.shouldShowPassword.collectAsState().value + val loginResult = loginViewModel.loginResult.collectAsState().value + + val coroutineScope = rememberCoroutineScope() + val snackBarHostState = remember {SnackbarHostState()} Scaffold( modifier = modifier, snackbarHost = { - SnackbarHost(hostState = snackbarHostState) + SnackbarHost(hostState = snackBarHostState) } ) { innerPadding -> Column( @@ -97,10 +96,10 @@ fun LoginScreen( // UserName 입력 필드 SignUpTextField( - text = userNameState, + text = userNameText.value, onValueChange = { newValue -> loginViewModel.onUserNameChange(newValue) - isUserNameValid = StringInputValidCheck(userNameState) + isUserNameValid = StringInputValidCheck(userNameText.value) }, fieldType = "UserName", conditionCheck = isUserNameValid, @@ -112,10 +111,10 @@ fun LoginScreen( // Password 입력 필드 SignUpTextField( - text = passwordState, + text = passwordText.value, onValueChange = { newValue -> loginViewModel.onPasswordChange(newValue) - isPasswordValid = PasswordValidCheck(passwordState) + isPasswordValid = PasswordValidCheck(passwordText.value) }, fieldType = "Password", conditionCheck = isPasswordValid, @@ -135,39 +134,34 @@ fun LoginScreen( var loginMessage = "" var loginSuccessFlag = 0 - if (loginViewModel.isLoginValid(userNameText, passwordText)) { + if (loginViewModel.isLoginValid(userNameText.value, passwordText.value)) { loginMessage = "로그인 성공" loginSuccessFlag = 1 - /* 로그인한 유저의 userName만 가지고 해당 유저의 정보 불러와야 함 */ - val loginedUser = RequestGetUserDto( - userName = userNameText, - ) + //입력받은 유저네임과 패스워드를 보내기 + coroutineScope.launch { + loginViewModel.logInUser() + } + //로그인 성공 시, token 값을 저장해 줌 - loginViewModel.logInUser(loginedUser, userViewModel) +// loginViewModel.logInUser(loginedUser, userViewModel) /*TODO: 백엔드 연결 후 해당 코드 삭제*/ - userViewModel.setUserName(userNameText) - - - - - - +// userViewModel.setUserName(userNameText) } else { loginMessage = "알맞은 유저 이름과 비밀번호를 입력하세요" } - scope.launch { - val snackbarResult = snackbarHostState.showSnackbar(loginMessage) - - if (loginSuccessFlag == 1 && snackbarResult == SnackbarResult.Dismissed) { - userViewModel.setUserName(userNameState) - navigateToHomeScreen() - } - } +// scope.launch { +// val snackbarResult = snackbarHostState.showSnackbar(loginMessage) +// +// if (loginSuccessFlag == 1 && snackbarResult == SnackbarResult.Dismissed) { +// userViewModel.setUserName(userNameState) +// navigateToHomeScreen() +// } +// } }, colors = ButtonDefaults.buttonColors(containerColor = Color.Blue), modifier = Modifier.fillMaxWidth() @@ -199,18 +193,13 @@ fun LoginScreen( -@Preview(showBackground = true) -@Composable -fun LoginScreenPreview2() { - ANDANDROIDTheme { - val scope = rememberCoroutineScope() - val snackbarHostState = remember { SnackbarHostState() } - LoginScreen( - scope = scope, - snackbarHostState = snackbarHostState, - userNameText = "", - passwordText = "", - navigateToHomeScreen = {}, - ) - } -} \ No newline at end of file +//@Preview(showBackground = true) +//@Composable +//fun LoginScreenPreview2() { +// ANDANDROIDTheme { +// val scope = rememberCoroutineScope() +// val snackbarHostState = remember { SnackbarHostState() } +// LoginScreen( +// ) +// } +//} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt index e10b13c..2d4b6f5 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt @@ -4,11 +4,16 @@ import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.and.model.dto.login.RequestGetUserDto -import org.sopt.and.model.dto.login.ResponseGetUserWrapperDto -import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto -import org.sopt.and.model.dto.signup.RequestCreateUserDto -import org.sopt.and.model.network.ServicePool +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.json.Json +import org.sopt.and.data.datalocal.datasource.UserInfoLocalDataSource +import org.sopt.and.domain.User +import org.sopt.and.data.dto.login.RequestGetUserDto +import org.sopt.and.data.dto.login.ResponseGetUserFailedDto +import org.sopt.and.data.dto.mypage.RequestGetUserHobbyDto +import org.sopt.and.data.dto.signup.RequestCreateUserDto +import org.sopt.and.data.dto.signup.ResponseCreateUserFailedDto +import org.sopt.and.data.network.ServicePool import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.presentation.mypageScreen.MypageViewModel import org.sopt.and.presentation.signupScreen.StringInputValidCheck @@ -18,19 +23,18 @@ import retrofit2.Callback import retrofit2.Response class LoginViewModel( - private val mypageViewModel: MypageViewModel + private val userInfoLocalDataSource: UserInfoLocalDataSource ) : ViewModel() { //로그인 요청을 서버로 보내기 위함 private val userService by lazy { ServicePool.userService } + private val _user = MutableStateFlow(User()) + val user: StateFlow = _user + private val _loginResult = MutableStateFlow(null) + val loginResult = _loginResult.asStateFlow() - private val _userNameState = MutableStateFlow("") - val userNameState: StateFlow = _userNameState - - private val _passwordState = MutableStateFlow("") - val passwordState: StateFlow = _passwordState private val _isUserNameValid = MutableStateFlow(false) val isUserNameValid: StateFlow = _isUserNameValid @@ -43,13 +47,13 @@ class LoginViewModel( //유저네임 입력 시 입력한 글자 표시 fun onUserNameChange(newUserName: String) { - _userNameState.value = newUserName + _user.value = _user.value.copy(name = newUserName) _isUserNameValid.value = StringInputValidCheck(newUserName) } //비밀번호 입력 시 입력한 글자 표시 fun onPasswordChange(newPassword: String) { - _passwordState.value = newPassword + _user.value = _user.value.copy(password = newPassword) _isPasswordValid.value = PasswordValidCheck(newPassword) } @@ -58,46 +62,45 @@ class LoginViewModel( _shouldShowPassword.value = !_shouldShowPassword.value } - // 로그인 검증 로직 + // 입력값이 조건에 맞는지 확인 (둘 다 여덟 자 이하?) fun isLoginValid(userNameText: String, passwordText: String): Boolean { - return _userNameState.value == userNameText && _passwordState.value == passwordText + return _isUserNameValid.value && _isPasswordValid.value } - fun logInUser(request: RequestGetUserDto, userViewModel: UserViewModel) { - val TAG = "UserService" - - userService.logInUser(request).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - val body = response.body() - if (body != null) { - val token = body.success.result.token - - Log.d(TAG, "유저 로그인 성공, 유저 토큰(id) : $token") - userViewModel.setLoginToken(token) - - val requestGetUserHobbyDto = RequestGetUserHobbyDto(token = token) - - val userHobby = mypageViewModel.getUserHobby(requestGetUserHobbyDto, userViewModel) - userViewModel.setHobby(userHobby.toString()) - - /*TODO: 이때의 token을 기반으로 user data 받아와 loginViewModel에 값 업데이트 해주기*/ - - - } else { - Log.e(TAG, "유저 로그인 실패") - } + suspend fun logInUser() { + val requestDto = RequestGetUserDto( + userName = _user.value.name, + password = _user.value.password + ) + + try { + val response = userService.logInUser(requestDto) + val token = response.body()?.result?.token + + if(response.isSuccessful && token != null) { + userInfoLocalDataSource.accessToken = token + userInfoLocalDataSource.userName = _user.value.name + _loginResult.value = true + Log.d( + "로그인 요청 성공", + "Status code: ${response.code()} token: $token" + ) + } else { + val errorBody = response.errorBody()?.string() + Log.e("서버 응답", "Raw response body: $errorBody") + val errorCode = if (errorBody != null) { + val errorData = Json.decodeFromString(errorBody) + errorData.code } else { - Log.e(TAG, "유저 로그인 에러 ${response.code()}") + "Unknown error code" } + Log.e("error", "Status code: ${response.code()} and error code: $errorCode") + _loginResult.value = false } + } catch (e: Exception) { + Log.e("error", "Exception: ${e.message}") + _loginResult.value = false + } - override fun onFailure(call: Call, t: Throwable) { - Log.e(TAG, "API 호출 도중 에러 발생: ${t.message}") - } - }) } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt index e8fae22..41ad0b3 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -4,10 +4,9 @@ import android.util.Log import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.and.model.dto.login.ResponseGetUserSuccessDto -import org.sopt.and.model.dto.mypage.RequestGetUserHobbyDto -import org.sopt.and.model.dto.mypage.ResponseGetUserHobbyWrapperDto -import org.sopt.and.model.network.ServicePool +import org.sopt.and.data.dto.mypage.RequestGetUserHobbyDto +import org.sopt.and.data.dto.mypage.ResponseGetUserHobbyWrapperDto +import org.sopt.and.data.network.ServicePool import retrofit2.Callback import org.sopt.and.presentation.main.UserViewModel diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt index fc7152b..d4916cf 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpScreen.kt @@ -26,7 +26,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import org.sopt.and.domain.User -import org.sopt.and.model.dto.signup.RequestCreateUserDto +import org.sopt.and.data.dto.signup.RequestCreateUserDto import org.sopt.and.presentation.main.UserViewModel import org.sopt.and.ui.components.SignUpandLogIn.SignUpTextField import org.sopt.and.ui.components.SignUpandLogIn.SocialLoginSection diff --git a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt index 23cd8bc..f757962 100644 --- a/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/signupScreen/SignUpViewModel.kt @@ -6,10 +6,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.serialization.json.Json import org.sopt.and.domain.User -import org.sopt.and.model.dto.signup.RequestCreateUserDto -import org.sopt.and.model.dto.signup.ResponseCreateUserFailedDto -import org.sopt.and.model.dto.signup.ResponseCreateUserSuccessDto -import org.sopt.and.model.network.ServicePool +import org.sopt.and.data.dto.signup.RequestCreateUserDto +import org.sopt.and.data.dto.signup.ResponseCreateUserFailedDto +import org.sopt.and.data.dto.signup.ResponseCreateUserSuccessDto +import org.sopt.and.data.network.ServicePool import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -25,9 +25,6 @@ class SignUpViewModel : ViewModel() { private val _signUpResult = MutableStateFlow?>(null) val signUpResult: StateFlow?> = _signUpResult -// val userNameText = _user.value.name -// val userPasswordText = _user.value.password -// val userHobbyText = _user.value.hobby private val _isUserNameValid = MutableStateFlow(true) val isUserNameValid: StateFlow = _isUserNameValid diff --git a/app/src/main/java/org/sopt/and/ui/components/BottomBar/CustomBottomAppBar.kt b/app/src/main/java/org/sopt/and/ui/components/BottomBar/CustomBottomAppBar.kt index 93db214..8324c5c 100644 --- a/app/src/main/java/org/sopt/and/ui/components/BottomBar/CustomBottomAppBar.kt +++ b/app/src/main/java/org/sopt/and/ui/components/BottomBar/CustomBottomAppBar.kt @@ -18,7 +18,6 @@ fun CustomBottomAppBar(navController: NavController) { BottomAppBar( containerColor = Color.Black, contentColor = Color.White, - ) { Row( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/org/sopt/and/ui/components/BottomBar/NavIcon.kt b/app/src/main/java/org/sopt/and/ui/components/BottomBar/NavIcon.kt index d3a8206..35bb9e3 100644 --- a/app/src/main/java/org/sopt/and/ui/components/BottomBar/NavIcon.kt +++ b/app/src/main/java/org/sopt/and/ui/components/BottomBar/NavIcon.kt @@ -16,6 +16,12 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController import org.sopt.and.R +import org.sopt.and.presentation.homeScreen.HomeScreen +import org.sopt.and.presentation.homeScreen.HomeViewModel +import org.sopt.and.presentation.mypageScreen.MypageScreen +import org.sopt.and.presentation.mypageScreen.MypageViewModel +import org.sopt.and.presentation.searchScreen.SearchScreen +import org.sopt.and.util.Route @Composable fun NavIcon( @@ -26,7 +32,19 @@ fun NavIcon( text: String ){ Column( - modifier = modifier.clickable {navController.navigate(route)}, + modifier = modifier.clickable { + when (route) { + "home" -> { + navController.navigate(Route.HomeScreen) + } + "search" -> { + navController.navigate(Route.SearchScreen) + } + "profile" -> { + navController.navigate(Route.MypageScreen(userName = "")) /*username을 여기다 어떻게 넣어주지?*/ + } + } + }, horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally ){ if(text != "MY"){ diff --git a/app/src/main/java/org/sopt/and/util/Constraints.kt b/app/src/main/java/org/sopt/and/util/Constraints.kt new file mode 100644 index 0000000..bc5a882 --- /dev/null +++ b/app/src/main/java/org/sopt/and/util/Constraints.kt @@ -0,0 +1,26 @@ +package org.sopt.and.util + +import kotlinx.serialization.Serializable + +@Serializable +sealed class Route { + @Serializable + data object HomeScreen : Route() + + @Serializable + data class SignUpScreen( + val userName: String, + val password: String + ) : Route() + + @Serializable + data object LoginScreen : Route() + + @Serializable + data class MypageScreen( + val userName: String + ) : Route() + + @Serializable + data object SearchScreen : Route() +} \ No newline at end of file From 5cbd2eeff30b696fccdc468d87e4416b9a911faf Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Tue, 3 Dec 2024 22:38:41 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat=20#week4=20:=20login=20Get=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../and/data/dto/login/ResponseGetUserDto.kt | 14 ++--- .../org/sopt/and/data/network/UserService.kt | 6 +- .../presentation/loginScreen/LoginScreen.kt | 14 ++--- .../mypageScreen/MypageViewModel.kt | 60 +++++++++---------- 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt b/app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt index a446493..01855a2 100644 --- a/app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/login/ResponseGetUserDto.kt @@ -13,10 +13,10 @@ data class ResponseGetUserFailedDto( data class ResponseGetUserDto( @SerialName("result") val result: UserResult -) - -@Serializable -data class UserResult( - @SerialName("token") - val token: String -) \ No newline at end of file +){ + @Serializable + data class UserResult( + @SerialName("token") + val token: String + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/network/UserService.kt b/app/src/main/java/org/sopt/and/data/network/UserService.kt index c82c33c..063aa1a 100644 --- a/app/src/main/java/org/sopt/and/data/network/UserService.kt +++ b/app/src/main/java/org/sopt/and/data/network/UserService.kt @@ -23,14 +23,14 @@ interface UserService { ): Response @POST("/login") - fun logInUser( + suspend fun logInUser( @Body request: RequestGetUserDto ): Response @GET("/user/my-hobby") - fun getMyHobby( + suspend fun getMyHobby( @Header("token") request: RequestGetUserHobbyDto - ): Call + ): Response diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt index 38fe6bf..5c06063 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt @@ -97,8 +97,9 @@ fun LoginScreen( // UserName 입력 필드 SignUpTextField( text = userNameText.value, - onValueChange = { newValue -> - loginViewModel.onUserNameChange(newValue) + onValueChange = { + userNameText.value = it + loginViewModel.onUserNameChange(it) isUserNameValid = StringInputValidCheck(userNameText.value) }, fieldType = "UserName", @@ -112,8 +113,9 @@ fun LoginScreen( // Password 입력 필드 SignUpTextField( text = passwordText.value, - onValueChange = { newValue -> - loginViewModel.onPasswordChange(newValue) + onValueChange = { + passwordText.value = it + loginViewModel.onPasswordChange(it) isPasswordValid = PasswordValidCheck(passwordText.value) }, fieldType = "Password", @@ -138,15 +140,11 @@ fun LoginScreen( loginMessage = "로그인 성공" loginSuccessFlag = 1 - //입력받은 유저네임과 패스워드를 보내기 coroutineScope.launch { loginViewModel.logInUser() } - //로그인 성공 시, token 값을 저장해 줌 -// loginViewModel.logInUser(loginedUser, userViewModel) - /*TODO: 백엔드 연결 후 해당 코드 삭제*/ // userViewModel.setUserName(userNameText) diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt index 41ad0b3..d492201 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -25,34 +25,34 @@ class MypageViewModel : ViewModel() { return _user.value.userName.toString() } - fun getUserHobby(request: RequestGetUserHobbyDto, userViewModel: UserViewModel) { - val TAG = "UserService" - - userService.getMyHobby(request).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - // 성공 응답 처리 - val body = response.body() - if (body != null) { - val userHobby = body.success.result?.userHobby ?: "default hobby" - Log.d("UserService", "유저 취미: $userHobby") - userViewModel.setHobby(userHobby) - } else { - Log.e("UserService", "응답 본문 없음") - } - } else { - // 실패 응답 처리 - } - } - - override fun onFailure(call: Call, t: Throwable) { - // 네트워크 오류 처리 - Log.e("UserService", "API 호출 실패: ${t.message}") - } - }) - - } +// fun getUserHobby(request: RequestGetUserHobbyDto, userViewModel: UserViewModel) { +// val TAG = "UserService" +// +// userService.getMyHobby(request).enqueue(object : Callback { +// override fun onResponse( +// call: Call, +// response: Response +// ) { +// if (response.isSuccessful) { +// // 성공 응답 처리 +// val body = response.body() +// if (body != null) { +// val userHobby = body.success.result?.userHobby ?: "default hobby" +// Log.d("UserService", "유저 취미: $userHobby") +// userViewModel.setHobby(userHobby) +// } else { +// Log.e("UserService", "응답 본문 없음") +// } +// } else { +// // 실패 응답 처리 +// } +// } +// +// override fun onFailure(call: Call, t: Throwable) { +// // 네트워크 오류 처리 +// Log.e("UserService", "API 호출 실패: ${t.message}") +// } +// }) +// +// } } \ No newline at end of file From 18cd80e70efed2039df09154a686c58c54ae1620 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Tue, 3 Dec 2024 23:05:48 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat=20#week4=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=84=B1=EA=B3=B5=20=ED=9B=84=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/loginScreen/LoginScreen.kt | 36 ++++++++----------- .../loginScreen/LoginViewModel.kt | 1 - 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt index 5c06063..884ca61 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -31,6 +32,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import org.sopt.and.presentation.signupScreen.StringInputValidCheck @@ -133,33 +135,12 @@ fun LoginScreen( // 로그인 버튼 Button( onClick = { - var loginMessage = "" - var loginSuccessFlag = 0 - + // 일단 글자 조건(8자)에 맞으면 logInUser API를 불러 봄 if (loginViewModel.isLoginValid(userNameText.value, passwordText.value)) { - loginMessage = "로그인 성공" - loginSuccessFlag = 1 - coroutineScope.launch { loginViewModel.logInUser() } - - - /*TODO: 백엔드 연결 후 해당 코드 삭제*/ -// userViewModel.setUserName(userNameText) - - } else { - loginMessage = "알맞은 유저 이름과 비밀번호를 입력하세요" } - -// scope.launch { -// val snackbarResult = snackbarHostState.showSnackbar(loginMessage) -// -// if (loginSuccessFlag == 1 && snackbarResult == SnackbarResult.Dismissed) { -// userViewModel.setUserName(userNameState) -// navigateToHomeScreen() -// } -// } }, colors = ButtonDefaults.buttonColors(containerColor = Color.Blue), modifier = Modifier.fillMaxWidth() @@ -186,6 +167,17 @@ fun LoginScreen( SocialLoginSection(modifier = modifier) Spacer(modifier = Modifier.weight(1f)) } + LaunchedEffect(loginResult){ + loginResult?.let { + if (loginResult == true) { + snackBarHostState.showSnackbar(message = "로그인에 성공했습니다.") + delay(300) + navigateToHomeScreen() + } else { + snackBarHostState.showSnackbar(message = "유저 이름 혹은 비밀번호를 확인하세요.") + } + } + } } } diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt index 2d4b6f5..7c1a464 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt @@ -35,7 +35,6 @@ class LoginViewModel( private val _loginResult = MutableStateFlow(null) val loginResult = _loginResult.asStateFlow() - private val _isUserNameValid = MutableStateFlow(false) val isUserNameValid: StateFlow = _isUserNameValid From 26c22c11f4a16c43cc017d3a67effe4515be1dfb Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Wed, 4 Dec 2024 00:42:27 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat=20#week4=20:=20=EC=B7=A8=EB=AF=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/MainActivity.kt | 2 +- .../data/dto/mypage/RequestGetUserHobbyDto.kt | 11 ---- .../dto/mypage/ResponseGetUserHobbyFailDto.kt | 10 ---- .../mypage/ResponseGetUserHobbySuccessDto.kt | 21 +++++--- .../mypage/ResponseGetUserHobbyWrapperDto.kt | 12 ----- .../org/sopt/and/data/network/UserService.kt | 7 ++- app/src/main/java/org/sopt/and/domain/User.kt | 3 +- .../loginScreen/LoginViewModel.kt | 1 - .../presentation/mypageScreen/MypageScreen.kt | 9 ++-- .../mypageScreen/MypageViewModel.kt | 53 ++++++++++++++----- .../MypageScreen/MyPageProfileSection.kt | 6 +-- 11 files changed, 66 insertions(+), 69 deletions(-) delete mode 100644 app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt delete mode 100644 app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt delete mode 100644 app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt diff --git a/app/src/main/java/org/sopt/and/MainActivity.kt b/app/src/main/java/org/sopt/and/MainActivity.kt index f85c040..1ee1b73 100644 --- a/app/src/main/java/org/sopt/and/MainActivity.kt +++ b/app/src/main/java/org/sopt/and/MainActivity.kt @@ -88,7 +88,7 @@ class MainActivity : ComponentActivity() { composable { backStackEntry -> val item = backStackEntry.toRoute() MypageScreen( - mypageViewModel = MypageViewModel(), + mypageViewModel = MypageViewModel(userInfoLocalDataSource = userInfoLocalDataSource), navController = navController ) } diff --git a/app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt deleted file mode 100644 index af0a098..0000000 --- a/app/src/main/java/org/sopt/and/data/dto/mypage/RequestGetUserHobbyDto.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.and.data.dto.mypage - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -//요청할 때는 token만 가지고 요청 -@Serializable -data class RequestGetUserHobbyDto( - @SerialName("token") - val token: String, -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt deleted file mode 100644 index ec1bbba..0000000 --- a/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyFailDto.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.and.data.dto.mypage - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseGetUserHobbyFailDto( - @SerialName("code") - val no: Int -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt index ace3010..efb25e7 100644 --- a/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt +++ b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbySuccessDto.kt @@ -4,13 +4,20 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ResponseGetUserHobbySuccessDto( - @SerialName("result") - val result: HobbyResult? = null, +data class ResponseGetUserHobbyFailDto( + @SerialName("code") + val no: Int ) @Serializable -data class HobbyResult( - @SerialName("hobby") - val userHobby: String? = null, -) +data class ResponseGetUserHobbyDto( + @SerialName("result") + val result: Result?= null, +){ + @Serializable + data class Result( + @SerialName("hobby") + val userHobby: String? = null, + ) +} + diff --git a/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt b/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt deleted file mode 100644 index b286830..0000000 --- a/app/src/main/java/org/sopt/and/data/dto/mypage/ResponseGetUserHobbyWrapperDto.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.sopt.and.data.dto.mypage - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ResponseGetUserHobbyWrapperDto( - @SerialName("success") - val success: ResponseGetUserHobbySuccessDto, - @SerialName("failed") - val failed: ResponseGetUserHobbyFailDto -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/network/UserService.kt b/app/src/main/java/org/sopt/and/data/network/UserService.kt index 063aa1a..8991ce8 100644 --- a/app/src/main/java/org/sopt/and/data/network/UserService.kt +++ b/app/src/main/java/org/sopt/and/data/network/UserService.kt @@ -2,8 +2,7 @@ package org.sopt.and.data.network import org.sopt.and.data.dto.login.RequestGetUserDto import org.sopt.and.data.dto.login.ResponseGetUserDto -import org.sopt.and.data.dto.mypage.RequestGetUserHobbyDto -import org.sopt.and.data.dto.mypage.ResponseGetUserHobbyWrapperDto +import org.sopt.and.data.dto.mypage.ResponseGetUserHobbyDto import org.sopt.and.data.dto.signup.RequestCreateUserDto import org.sopt.and.data.dto.signup.ResponseCreateUserSuccessDto import retrofit2.Call @@ -29,8 +28,8 @@ interface UserService { @GET("/user/my-hobby") suspend fun getMyHobby( - @Header("token") request: RequestGetUserHobbyDto - ): Response + @Header("token") token: String + ): Response diff --git a/app/src/main/java/org/sopt/and/domain/User.kt b/app/src/main/java/org/sopt/and/domain/User.kt index 623e220..7d05423 100644 --- a/app/src/main/java/org/sopt/and/domain/User.kt +++ b/app/src/main/java/org/sopt/and/domain/User.kt @@ -3,5 +3,6 @@ package org.sopt.and.domain data class User( var name: String = "", var password: String = "", - var hobby: String = "" + var hobby: String = "", + var accessToken: String = "", ) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt index 7c1a464..9de63ba 100644 --- a/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/loginScreen/LoginViewModel.kt @@ -10,7 +10,6 @@ import org.sopt.and.data.datalocal.datasource.UserInfoLocalDataSource import org.sopt.and.domain.User import org.sopt.and.data.dto.login.RequestGetUserDto import org.sopt.and.data.dto.login.ResponseGetUserFailedDto -import org.sopt.and.data.dto.mypage.RequestGetUserHobbyDto import org.sopt.and.data.dto.signup.RequestCreateUserDto import org.sopt.and.data.dto.signup.ResponseCreateUserFailedDto import org.sopt.and.data.network.ServicePool diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt index 165c184..4d129bd 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -30,12 +31,10 @@ import org.sopt.and.presentation.main.UserViewModel @Composable fun MypageScreen( navController: NavController, - userViewModel: UserViewModel = viewModel(), mypageViewModel: MypageViewModel = viewModel() ) { - var userNameText = userViewModel.userName.collectAsState().value - val userHobby = userViewModel.hobby.collectAsState().value + val user by mypageViewModel.user.collectAsState() Scaffold( bottomBar = { @@ -49,8 +48,8 @@ fun MypageScreen( .padding(innerPadding) ) { MyPageProfileSection( - deliveredUserName = userNameText, - deliveredUserHobby = userHobby + deliveredUserName = user.name, + deliveredUserHobby = user.hobby ) Spacer(modifier = Modifier.height(0.5.dp)) MyPageProfileSection2( diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt index d492201..8f9154d 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -2,29 +2,58 @@ package org.sopt.and.presentation.mypageScreen import android.util.Log import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.and.data.dto.mypage.RequestGetUserHobbyDto -import org.sopt.and.data.dto.mypage.ResponseGetUserHobbyWrapperDto +import kotlinx.coroutines.launch +import org.sopt.and.data.datalocal.datasource.UserInfoLocalDataSource import org.sopt.and.data.network.ServicePool -import retrofit2.Callback - +import org.sopt.and.domain.User import org.sopt.and.presentation.main.UserViewModel -import retrofit2.Call -import retrofit2.Response -class MypageViewModel : ViewModel() { - //현재 로그인한 토큰으로 현 사용자의 취미를 불러오기 위함 +class MypageViewModel( + private val userInfoLocalDataSource: UserInfoLocalDataSource +) : ViewModel() { + private val userService by lazy { ServicePool.userService } - private val _user = MutableStateFlow(UserViewModel()) - val user: StateFlow = _user + private val _user = MutableStateFlow(User()) + val user: StateFlow = _user + + init { + loadUserData() + } + + fun loadUserData() { + viewModelScope.launch { + val userName = userInfoLocalDataSource.userName + val accessToken = userInfoLocalDataSource.accessToken + val hobby = getUserHobby() + _user.value.name = userName + _user.value.hobby = hobby + _user.value.accessToken = accessToken + } + } - fun getUserName(): String { - return _user.value.userName.toString() + suspend fun getUserHobby(): String { + try { + val response = userService.getMyHobby(userInfoLocalDataSource.accessToken) + if (response.isSuccessful) { + Log.d("취미 조회 API 성공", "status code: ${response.code()}") + return response.body()?.result?.userHobby ?: "hobby" + } else { + Log.d("에러", "status code: ${response.code()}") + return "취미 가져오기 에러" + } + } catch (e: Exception) { + Log.e("error", "Exception: ${e.message}") + return "취미 가져오기 에러" + } } + + // fun getUserHobby(request: RequestGetUserHobbyDto, userViewModel: UserViewModel) { // val TAG = "UserService" // diff --git a/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt b/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt index be2dc2e..9d8ceb6 100644 --- a/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt +++ b/app/src/main/java/org/sopt/and/ui/components/MypageScreen/MyPageProfileSection.kt @@ -30,10 +30,6 @@ fun MyPageProfileSection( deliveredUserName: String, deliveredUserHobby: String?, ){ - - //임시 값 삽입 - val hobby = deliveredUserHobby ?: "eating" - Column( modifier = Modifier .fillMaxWidth() @@ -56,7 +52,7 @@ fun MyPageProfileSection( ) Spacer(modifier = Modifier.width(10.dp)) Text( - "요즘 ${hobby}를 즐기는\n${deliveredUserName}님", + "${deliveredUserHobby}를 즐기는\n${deliveredUserName}님", color = Color.White ) Spacer(modifier = Modifier.weight(0.5f)) From 0512b01062e362ddb0698c510987b92af98801a9 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Wed, 4 Dec 2024 02:08:12 +0900 Subject: [PATCH 10/11] =?UTF-8?q?fix=20#week4=20:=20NavHost=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20innerPadding=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/MainActivity.kt | 131 ++++++++---------- .../and/presentation/homeScreen/HomeScreen.kt | 66 ++------- .../presentation/mypageScreen/MypageScreen.kt | 1 - .../mypageScreen/MypageViewModel.kt | 34 ----- .../ui/components/TopBar/CustopTopAppBar.kt | 9 ++ 5 files changed, 73 insertions(+), 168 deletions(-) diff --git a/app/src/main/java/org/sopt/and/MainActivity.kt b/app/src/main/java/org/sopt/and/MainActivity.kt index 1ee1b73..4617c34 100644 --- a/app/src/main/java/org/sopt/and/MainActivity.kt +++ b/app/src/main/java/org/sopt/and/MainActivity.kt @@ -5,13 +5,8 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import org.sopt.and.ui.theme.ANDANDROIDTheme @@ -34,88 +29,70 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { ANDANDROIDTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - val navController = rememberNavController() + Scaffold( + modifier = Modifier.fillMaxSize(), + content = { it + val navController = rememberNavController() - val context = navController.context - val userInfoLocalDataSource = UserInfoLocalDataSourceImpl(context) + val context = navController.context + val userInfoLocalDataSource = UserInfoLocalDataSourceImpl(context) - NavHost( - navController = navController, - startDestination = Route.SignUpScreen(userName = "", password = ""), - modifier = Modifier.padding(innerPadding) - ){ - composable { backStackEntry -> - val item = backStackEntry.toRoute() - SignUpScreen( - navigateToLoginScreen = { - navController.navigate(Route.LoginScreen){ - popUpTo { inclusive = true } - launchSingleTop = true + NavHost( + navController = navController, + startDestination = Route.SignUpScreen(userName = "", password = ""), + modifier = Modifier + ){ + composable { backStackEntry -> + val item = backStackEntry.toRoute() + SignUpScreen( + navigateToLoginScreen = { + navController.navigate(Route.LoginScreen){ + popUpTo { inclusive = true } + launchSingleTop = true + } } - } - ) - } + ) + } - composable { backStackEntry -> - val item = backStackEntry.toRoute() - LoginScreen( - loginViewModel = LoginViewModel(userInfoLocalDataSource = userInfoLocalDataSource), - navigateToHomeScreen = { - navController.navigate(Route.HomeScreen){ - popUpTo { inclusive = true} - launchSingleTop = true - } - }, - ) - } + composable { backStackEntry -> + val item = backStackEntry.toRoute() + LoginScreen( + loginViewModel = LoginViewModel(userInfoLocalDataSource = userInfoLocalDataSource), + navigateToHomeScreen = { + navController.navigate(Route.HomeScreen){ + popUpTo { inclusive = true} + launchSingleTop = true + } + }, + ) + } - composable { backStackEntry -> - val item = backStackEntry.toRoute() - HomeScreen( - homeViewModel = HomeViewModel(), - navController = navController, - ) - } + composable { backStackEntry -> + val item = backStackEntry.toRoute() + HomeScreen( + homeViewModel = HomeViewModel(), + navController = navController, + ) + } - composable { backStackEntry -> - val item = backStackEntry.toRoute() - SearchScreen( - navController = navController - ) - } + composable { backStackEntry -> + val item = backStackEntry.toRoute() + SearchScreen( + navController = navController + ) + } - composable { backStackEntry -> - val item = backStackEntry.toRoute() - MypageScreen( - mypageViewModel = MypageViewModel(userInfoLocalDataSource = userInfoLocalDataSource), - navController = navController - ) + composable { backStackEntry -> + val item = backStackEntry.toRoute() + MypageScreen( + mypageViewModel = MypageViewModel(userInfoLocalDataSource = userInfoLocalDataSource), + navController = navController + ) + } } -// composable("home") { -// HomeScreen( -// navController = navController, -// homeViewModel = HomeViewModel() -// ) -// } -// -// composable("search") { -// SearchScreen( -// navController = navController -// ) -// } - -// composable("profile") { -// -// MypageScreen( -// navController = navController, -// mypageViewModel = mypageViewModel -// ) -// } - } - } + }) } } } diff --git a/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt b/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt index 0026dc2..4c4d989 100644 --- a/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/homeScreen/HomeScreen.kt @@ -1,10 +1,8 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - package org.sopt.and.presentation.homeScreen -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -16,7 +14,6 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -41,14 +38,10 @@ import org.sopt.and.ui.components.TopBar.CustomTopAppBarSecond import org.sopt.and.ui.theme.ANDANDROIDTheme -//@Serializable -//data object HomeScreen - -@OptIn(ExperimentalFoundationApi::class) @Composable fun HomeScreen( modifier: Modifier = Modifier, - navController: NavController, // navController를 넘겨 받아 사용 + navController: NavController, homeViewModel: HomeViewModel = viewModel() ) { val context = LocalContext.current @@ -56,7 +49,9 @@ fun HomeScreen( Scaffold( topBar = { - Column { + Column( + modifier = modifier.fillMaxWidth() + ){ CustomTopAppBar(navController = navController) CustomTopAppBarSecond(navController = navController) } @@ -64,13 +59,12 @@ fun HomeScreen( bottomBar = { CustomBottomAppBar(navController = navController) } - ) { innerPadding -> + ) { it Column( modifier = Modifier .fillMaxSize() .verticalScroll(scrollState) .background(Color(0xFF1B1B1B)) - .padding(innerPadding) // 패딩 적용 .padding(all = 10.dp) ) { @@ -144,49 +138,9 @@ fun HomeScreenPreview() { val navController = rememberNavController() val homeViewModel = HomeViewModel() - ANDANDROIDTheme { - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - Column{ - CustomTopAppBar(navController = navController) - CustomTopAppBarSecond(navController = navController) - - } - - }, - bottomBar = { - CustomBottomAppBar(navController = navController) - } - ) { - innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .background(Color(0xFF1B1B1B)) - .padding(innerPadding) - ){ - NavHost( - navController = navController, - startDestination = "home", - ){ - composable("home") {HomeScreen( - navController = navController, - homeViewModel = homeViewModel - )} - composable("search") { - SearchScreen( - navController = navController - ) - } - composable("profile") { - MypageScreen( - navController = navController, - ) - } - } - } + HomeScreen( + navController = navController, + homeViewModel = homeViewModel + ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt index 4d129bd..00582be 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageScreen.kt @@ -33,7 +33,6 @@ fun MypageScreen( navController: NavController, mypageViewModel: MypageViewModel = viewModel() ) { - val user by mypageViewModel.user.collectAsState() Scaffold( diff --git a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt index 8f9154d..7fb2579 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypageScreen/MypageViewModel.kt @@ -9,8 +9,6 @@ import kotlinx.coroutines.launch import org.sopt.and.data.datalocal.datasource.UserInfoLocalDataSource import org.sopt.and.data.network.ServicePool import org.sopt.and.domain.User -import org.sopt.and.presentation.main.UserViewModel - class MypageViewModel( private val userInfoLocalDataSource: UserInfoLocalDataSource @@ -52,36 +50,4 @@ class MypageViewModel( } } - - -// fun getUserHobby(request: RequestGetUserHobbyDto, userViewModel: UserViewModel) { -// val TAG = "UserService" -// -// userService.getMyHobby(request).enqueue(object : Callback { -// override fun onResponse( -// call: Call, -// response: Response -// ) { -// if (response.isSuccessful) { -// // 성공 응답 처리 -// val body = response.body() -// if (body != null) { -// val userHobby = body.success.result?.userHobby ?: "default hobby" -// Log.d("UserService", "유저 취미: $userHobby") -// userViewModel.setHobby(userHobby) -// } else { -// Log.e("UserService", "응답 본문 없음") -// } -// } else { -// // 실패 응답 처리 -// } -// } -// -// override fun onFailure(call: Call, t: Throwable) { -// // 네트워크 오류 처리 -// Log.e("UserService", "API 호출 실패: ${t.message}") -// } -// }) -// -// } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/ui/components/TopBar/CustopTopAppBar.kt b/app/src/main/java/org/sopt/and/ui/components/TopBar/CustopTopAppBar.kt index 95ff0a3..7db3b3d 100644 --- a/app/src/main/java/org/sopt/and/ui/components/TopBar/CustopTopAppBar.kt +++ b/app/src/main/java/org/sopt/and/ui/components/TopBar/CustopTopAppBar.kt @@ -17,8 +17,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import org.sopt.and.R @Composable @@ -55,4 +57,11 @@ fun CustomTopAppBar(navController: NavController){ ) } ) +} + +@Preview(showBackground = true) +@Composable +fun AppbarFirstPreivew(){ + val navController = rememberNavController() + CustomTopAppBar(navController = navController) } \ No newline at end of file From f6f4b09e9c2178b70406159a9479fb11030caf69 Mon Sep 17 00:00:00 2001 From: tunaunnie Date: Wed, 4 Dec 2024 02:20:18 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix=20#week4=20:=20BaseUrl=20=EC=88=A8?= =?UTF-8?q?=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/org/sopt/and/data/network/ApiFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/sopt/and/data/network/ApiFactory.kt b/app/src/main/java/org/sopt/and/data/network/ApiFactory.kt index 5efe441..9e7ad20 100644 --- a/app/src/main/java/org/sopt/and/data/network/ApiFactory.kt +++ b/app/src/main/java/org/sopt/and/data/network/ApiFactory.kt @@ -5,10 +5,11 @@ import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import org.sopt.and.BuildConfig import retrofit2.Retrofit object ApiFactory { - private const val BASE_URL: String = "http://223.130.135.50:8085/" + private const val BASE_URL: String = BuildConfig.BASE_URL private val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY