diff --git a/app/build.gradle b/app/build.gradle index bef3c17..2fa725c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,10 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.0' } +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) android { namespace 'com.sopt.now.compose' @@ -18,6 +21,8 @@ android { vectorDrawables { useSupportLibrary true } + + buildConfigField "String", "AUTH_BASE_URL", properties["base.url"] } buildTypes { @@ -35,6 +40,7 @@ android { } buildFeatures { compose true + buildConfig true } composeOptions { kotlinCompilerExtensionVersion '1.5.1' @@ -63,4 +69,17 @@ dependencies { androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' + + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0' + +// define a BOM and its version + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) + +// define any required OkHttp artifacts without version + implementation("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:okhttp:4.9.1") + implementation("com.squareup.okhttp3:logging-interceptor") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b468b1..5936271 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,10 @@ + - @@ -29,7 +30,7 @@ android:label="@string/app_name" android:theme="@style/Theme.NOWSOPTAndroid" /> diff --git a/app/src/main/java/com/sopt/now/compose/ApiFactory.kt b/app/src/main/java/com/sopt/now/compose/ApiFactory.kt new file mode 100644 index 0000000..79980fd --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ApiFactory.kt @@ -0,0 +1,22 @@ +package com.sopt.now.compose + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import retrofit2.Retrofit +object ApiFactory { + private const val BASE_URL: String = BuildConfig.AUTH_BASE_URL + + val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + inline fun create(): T = retrofit.create(T::class.java) +} + +object ServicePool { + val authService = ApiFactory.create() +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/AuthService.kt b/app/src/main/java/com/sopt/now/compose/AuthService.kt new file mode 100644 index 0000000..e3da13e --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/AuthService.kt @@ -0,0 +1,28 @@ +package com.sopt.now.compose + +import com.sopt.now.compose.Dto.RequestLoginDto +import com.sopt.now.compose.Dto.RequestSignUpDto +import com.sopt.now.compose.Dto.ResponseLoginDto +import com.sopt.now.compose.Dto.ResponseSignUpDto +import com.sopt.now.compose.Dto.ResponseUserInfoDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.POST +interface AuthService { + @POST("member/join") + fun signUp( + @Body request: RequestSignUpDto, + ): Call + + @POST("member/login") + fun login( + @Body request: RequestLoginDto + ): Call + + @GET("member/info") + fun getUserInfo( + @Header("memberid") memberId: String + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/Dto/RequestLoginDto.kt b/app/src/main/java/com/sopt/now/compose/Dto/RequestLoginDto.kt new file mode 100644 index 0000000..39a12d0 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Dto/RequestLoginDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.Dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestLoginDto( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("password") + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/Dto/RequestSignUpDto.kt b/app/src/main/java/com/sopt/now/compose/Dto/RequestSignUpDto.kt new file mode 100644 index 0000000..de870dd --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Dto/RequestSignUpDto.kt @@ -0,0 +1,16 @@ +package com.sopt.now.compose.Dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestSignUpDto( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("password") + val password: String, + @SerialName("nickname") + val nickname: String, + @SerialName("phone") + val phone: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/Dto/ResponseLoginDto.kt b/app/src/main/java/com/sopt/now/compose/Dto/ResponseLoginDto.kt new file mode 100644 index 0000000..87c3dd9 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Dto/ResponseLoginDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.Dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseLoginDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/Dto/ResponseSignUpDto.kt b/app/src/main/java/com/sopt/now/compose/Dto/ResponseSignUpDto.kt new file mode 100644 index 0000000..8ea0303 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Dto/ResponseSignUpDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.Dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseSignUpDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/Dto/ResponseUserInfoDto.kt b/app/src/main/java/com/sopt/now/compose/Dto/ResponseUserInfoDto.kt new file mode 100644 index 0000000..3417135 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Dto/ResponseUserInfoDto.kt @@ -0,0 +1,24 @@ +package com.sopt.now.compose.Dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseUserInfoDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, + @SerialName("data") + val data: UserData, +) + +@Serializable +data class UserData( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("nickname") + val nickname: String, + @SerialName("phone") + val phone: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/HomeView.kt b/app/src/main/java/com/sopt/now/compose/HomeScreen.kt similarity index 98% rename from app/src/main/java/com/sopt/now/compose/HomeView.kt rename to app/src/main/java/com/sopt/now/compose/HomeScreen.kt index 914f2b3..4f6cce5 100644 --- a/app/src/main/java/com/sopt/now/compose/HomeView.kt +++ b/app/src/main/java/com/sopt/now/compose/HomeScreen.kt @@ -15,14 +15,13 @@ sealed class HomeData { data class UserInfoData(val userInfo: UserInfo) : HomeData() data class FriendData(val friend: Friend) : HomeData() } - val homeDataList = listOf( HomeData.UserInfoData( userInfo = UserInfo( id = "nagaeng", password = "qazwsx!@#", nickname = "이나경", - mbti = "ENTJ" + phone = "010-3412-1683" ) ), HomeData.FriendData( @@ -112,5 +111,4 @@ fun HomeView(homeDataList: List) { } } } -} - +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/MypageScreen.kt b/app/src/main/java/com/sopt/now/compose/MypageScreen.kt new file mode 100644 index 0000000..f284c16 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/MypageScreen.kt @@ -0,0 +1,117 @@ +package com.sopt.now.compose + +import android.content.Context +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sopt.now.compose.Dto.ResponseUserInfoDto +import com.sopt.now.compose.user.UserInfo +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine +@Composable +fun MyPageFragment(context: Context, userId: String) { + var userInfo by remember { mutableStateOf(null) } + + LaunchedEffect(userId) { + try { + userInfo = getUserInfo(userId) + } catch (e: Exception) { + Log.e("MyPageFragment", "Error: ${e.message}") + } + } + + userInfo?.let { user -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 30.dp, vertical = 10.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.width(20.dp)) + Text( + text = user.nickname, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + ) + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = "ID", + fontSize = 25.sp + ) + Text( + text = user.id, + fontSize = 20.sp, + color = Color.Gray + ) + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = "Phone", + fontSize = 25.sp + ) + Text( + text = user.phone, + fontSize = 20.sp, + color = Color.Gray + ) + } + } + } +} + +suspend fun getUserInfo(userId: String): UserInfo { + return suspendCoroutine { continuation -> + ServicePool.authService.getUserInfo(userId).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val data: ResponseUserInfoDto? = response.body() + data?.let { + continuation.resume( + UserInfo( + it.data.authenticationId, + "", + it.data.nickname, + it.data.phone + ) + ) + } + } else { + continuation.resumeWithException(Exception("Failed to fetch user info")) + } + } + + override fun onFailure(call: Call, t: Throwable) { + continuation.resumeWithException(t) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/MypageView.kt b/app/src/main/java/com/sopt/now/compose/MypageView.kt deleted file mode 100644 index 6b344fc..0000000 --- a/app/src/main/java/com/sopt/now/compose/MypageView.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.sopt.now.compose - -import android.content.Intent -import android.os.Bundle -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.sopt.now.compose.ui.theme.PurpleGrey40 -import com.sopt.now.compose.user.UserInfo -@Composable -fun MypageView(userInfo: UserInfo) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(30.dp), - verticalArrangement = Arrangement.spacedBy(40.dp), - ) { - if (userInfo != null) { - Image( - painter = painterResource(id = R.drawable.apple), - contentDescription = null, - modifier = Modifier - .size(110.dp) - .aspectRatio(1f) - ) - Text(text = "안녕하세요, ${userInfo.nickname}님", - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - ) - Spacer(modifier = Modifier.height(15.dp)) - Text(text = userInfo.id) - Spacer(modifier = Modifier.height(15.dp)) - Text(text = userInfo.password) - } else { - Text( - text = "사용자 정보를 불러올 수 없습니다.", - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.fillMaxWidth() - ) - } - } -}@Composable -fun UserInfoComposable(id: String, password: String, nickname: String) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text(text = "안녕하세요, $nickname 님!\n", - fontSize = 24.sp, - fontWeight = FontWeight.ExtraBold) - - Text(text = "아이디: $id \n", - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = PurpleGrey40) - - Text(text = "비밀번호: $password \n", - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = PurpleGrey40) - - - Button( - onClick = { }, - - contentPadding = PaddingValues(start = 90.dp, end = 90.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.LightGray, - contentColor = Color.Black - ) - ) { - Text(text = "수정하기", - fontWeight = FontWeight.Bold, - ) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/activity/LoginActivity.kt b/app/src/main/java/com/sopt/now/compose/activity/LoginActivity.kt index b2e6211..32406ef 100644 --- a/app/src/main/java/com/sopt/now/compose/activity/LoginActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/activity/LoginActivity.kt @@ -3,185 +3,166 @@ package com.sopt.now.compose.activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme +import androidx.lifecycle.ViewModel +import com.sopt.now.compose.Dto.RequestLoginDto +import com.sopt.now.compose.Dto.ResponseLoginDto +import com.sopt.now.compose.ServicePool +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response class LoginActivity : ComponentActivity() { + private val viewModel by lazy { LoginViewModel() } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - val getId = intent.getStringExtra("id") ?: "" - val getPassword = intent.getStringExtra("password") ?: "" - val getNickname = intent.getStringExtra("nickname") ?: "" - val getMbti = intent.getStringExtra("mbti") ?: "" - setContent { - LoginContent(getId, getPassword, getNickname, getMbti) + LoginContent(viewModel) } } } @Composable -fun LoginContent(getId: String, getPassword: String, getNickname: String, getMbti: String) { - var isLogged by remember { mutableStateOf(false) } - - if (!isLogged) { - SoptComposable( - getId = getId, - getPassword = getPassword, - getNickname = getNickname, - getMbti = getMbti - ) - } else { - val context = LocalContext.current - context.startActivity(Intent(context, MainActivity::class.java)) - } -} - -fun isValid(userId: String?, userPassword: String?, getId: String, getPassword: String): Boolean { - return userId != "" && userPassword != "" && userId == getId && userPassword == getPassword -} - -fun Success(context: Context, userId: String, userPassword: String, userNickname: String, userMbti: String?) { - - val intent = Intent(context, MainActivity::class.java).apply { - putExtra("id", userId) - putExtra("password", userPassword) - putExtra("nickname", userNickname) - putExtra("mbti", userMbti) - } - context.startActivity(intent) - Toast.makeText(context, "로그인 성공!", Toast.LENGTH_SHORT).show() -} - -@Composable -fun SoptComposable( - getId: String, - getPassword: String, - getNickname: String, - getMbti: String - -) { - var userId by remember { mutableStateOf("") } - var userPassword by remember { mutableStateOf("") } - var userNickname by remember { mutableStateOf("") } - var userMbti by remember { mutableStateOf("") } +fun LoginContent(viewModel: LoginViewModel) { + val context = LocalContext.current Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxSize() + .padding(30.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(30.dp)) + var userId by remember { mutableStateOf("") } + var userPassword by remember { mutableStateOf("") } Text( - text = "Welcome To SOPT", - color = Color.Gray, - fontWeight = FontWeight.Bold, - fontSize = 30.sp, - modifier = Modifier.align(Alignment.CenterHorizontally), + text = "LOGIN PAGE", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center ) - // Welcome 문구와 ID 텍스트 사이의 간격 - Spacer(modifier = Modifier.height(100.dp)) // 중간 간격을 위한 투명 view - - // ID 입력하는 부분 (ID 텍스트는 좌측 정렬, 텍스트 필드는 중앙 정렬) - Column(verticalArrangement = Arrangement.Center) { - Text(text = "ID") - - TextField( - value = userId, - onValueChange = { userId = it }, - label = { Text("ID를 입력하세요") }, - placeholder = { Text("EX) helen0816") }, - singleLine = true, - ) - } - - // ID 텍스트 필드와 PASSWORD 텍스트 사이의 간격 - Spacer(modifier = Modifier.height(50.dp)) // 중간 간격을 위한 투명 view - - // PW 입력하는 부분 (PW 텍스트는 좌측 정렬, 텍스트 필드는 중앙 정렬) - Column(verticalArrangement = Arrangement.Center) { - Text(text = "PASSWORD") + Spacer(modifier = Modifier.height(30.dp)) - TextField( - value = userPassword, - onValueChange = { userPassword = it }, - label = { Text("PASSWORD를 입력하세요") }, - placeholder = { Text("EX) qwaszx@!") }, - singleLine = true, - ) - } + SignUpTextField( + value = userId, + onValueChange = { userId = it }, + label = "ID", + hint = "Enter your ID" + ) - // PW 텍스트 필드와 버튼 사이의 간격 - Spacer(modifier = Modifier.height(250.dp)) // 중간 간격을 위한 투명 view + Spacer(modifier = Modifier.height(20.dp)) - val context = LocalContext.current + LoginTextField( + value = userPassword, + onValueChange = { userPassword = it }, + label = "Password", + hint = "Enter your password", + isPassword = true + ) + Spacer(modifier = Modifier.height(30.dp)) - Button( - onClick = { if (isValid(userId, userPassword, getId ?: "", getPassword ?: "")) { - Success(context, userId, userPassword, userNickname, userMbti) - } - else Toast.makeText(context, "아이디 또는 비밀번호가 일치하지 않습니다", Toast.LENGTH_SHORT).show() - }, - contentPadding = PaddingValues(start = 90.dp, end = 90.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.LightGray, - contentColor = Color.Black - ) - ) { - Text(text = "로그인", - fontWeight = FontWeight.Bold, - ) + Button(onClick = { + viewModel.login(userId, userPassword, context) + }) { + Text("로그인") } - Spacer(modifier = Modifier.height(10.dp)) - Button( - onClick = { - val intent = Intent(context, SignupActivity::class.java) - context.startActivity(intent) - }, - contentPadding = PaddingValues(start = 90.dp, end = 90.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.LightGray, - contentColor = Color.Black - ) - ) - { - Text(text = "회원가입", - fontWeight = FontWeight.Bold, - ) + // 회원가입으로 이동할 수 있는 버튼 추가 + Button(onClick = { + val intent = Intent(context, SignUpActivity::class.java) + context.startActivity(intent) + }) { + Text("회원가입") } - } } -@Preview(showBackground = true) @Composable -fun LoginPreview() { - NOWSOPTAndroidTheme { - SoptComposable(getId = "", getPassword = "", getNickname = "", getMbti = "") +fun LoginTextField ( + value: String, + onValueChange: (String) -> Unit, + label: String, + hint: String, + isPassword: Boolean = false +) { + TextField( + value = value, + onValueChange = onValueChange, + label = { Text(label) }, + placeholder = { Text(hint) }, + singleLine = true, + modifier = Modifier + .fillMaxWidth(), + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None + ) +} + +class LoginViewModel : ViewModel() { + private val _loginState = mutableStateOf(LoginState()) + fun login( + userId: String, + userPassword: String, + context: Context + ) { + val loginRequestDto = RequestLoginDto(userId, userPassword) + val authService = ServicePool.authService + + authService.login(loginRequestDto).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val userId = response.headers()["location"] + _loginState.value = LoginState(isSuccess = true, message = "로그인 성공!") + Log.e("userId", "login success for $userId") + + // 로그인 성공 시 메인(홈) 화면으로 이동 + val intent = Intent(context, MainActivity::class.java) + context.startActivity(intent) + intent.putExtra("userId", userId) + // 로그인 성공 메시지 토스트로 띄우기 + Toast.makeText(context, "로그인 성공! 유저 ID: $userId\"", Toast.LENGTH_SHORT).show() + } else { + val errorMessage = response.errorBody()?.string() ?: "Unknown error" + _loginState.value = LoginState(isSuccess = false, message = errorMessage) + // 회원가입 실패 시 실패 원인 토스트로 띄우기 + Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() + } + } + + override fun onFailure(call: Call, t: Throwable) { + val errorMessage = "서버 통신 오류: ${t.localizedMessage}" + _loginState.value = LoginState(isSuccess = false, message = errorMessage) + // 서버 통신 오류 시 토스트로 띄우기 + Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() + } + }) } } +data class LoginState( + val isSuccess: Boolean = false, + val message: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/activity/MainActivity.kt b/app/src/main/java/com/sopt/now/compose/activity/MainActivity.kt index ff64dc0..b327fd4 100644 --- a/app/src/main/java/com/sopt/now/compose/activity/MainActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/activity/MainActivity.kt @@ -1,6 +1,7 @@ package com.sopt.now.compose.activity import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* @@ -33,7 +34,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.sopt.now.compose.BottomNaviItem import com.sopt.now.compose.HomeView -import com.sopt.now.compose.MypageView import com.sopt.now.compose.homeDataList import com.sopt.now.compose.user.UserInfo @@ -57,10 +57,23 @@ class MainActivity : ComponentActivity() { const val USER_INFO = "user_info" } } +@Composable +fun MyPageView(userInfo: UserInfo) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = "User Info") + Text(text = "ID: ${userInfo.id}") + Text(text = "Nickname: ${userInfo.nickname}") + Text(text = "Phone: ${userInfo.phone}") + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScaffold(userInfo: UserInfo?) { - var presses by remember { mutableIntStateOf(0) } var selectedItem by remember { mutableIntStateOf(0) } val items = listOf( BottomNaviItem( @@ -106,18 +119,17 @@ fun MainScaffold(userInfo: UserInfo?) { .padding(innerPadding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - when(selectedItem) { + when (selectedItem) { 0 -> { HomeView(homeDataList = homeDataList) } - 1 -> { - // search 페이지 구현 x - } + 2 -> { if (userInfo != null) { - MypageView(userInfo = userInfo) + MyPageView(userInfo = userInfo) } - + else + Log.e("MyPageView", "User information not available") } } } diff --git a/app/src/main/java/com/sopt/now/compose/activity/SignUpActivity.kt b/app/src/main/java/com/sopt/now/compose/activity/SignUpActivity.kt new file mode 100644 index 0000000..aa7679b --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/activity/SignUpActivity.kt @@ -0,0 +1,178 @@ +package com.sopt.now.compose.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModel +import com.sopt.now.compose.Dto.RequestSignUpDto +import com.sopt.now.compose.Dto.ResponseSignUpDto +import com.sopt.now.compose.ServicePool +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class SignUpActivity : ComponentActivity() { + private val viewModel by lazy { SignUpViewModel() } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + SignUpContent(viewModel) + } + } +} + +@Composable +fun SignUpContent(viewModel: SignUpViewModel) { + // 현재 컨텍스트 가져오기 + val context = LocalContext.current + + Column( + modifier = Modifier + .fillMaxSize() + .padding(30.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + var userId by remember { mutableStateOf("") } + var userPassword by remember { mutableStateOf("") } + var userNickname by remember { mutableStateOf("") } + var userPhone by remember { mutableStateOf("") } + + Text( + text = "SIGN UP PAGE", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(30.dp)) + + SignUpTextField( + value = userId, + onValueChange = { userId = it }, + label = "ID", + hint = "Enter your ID" + ) + + Spacer(modifier = Modifier.height(20.dp)) + + SignUpTextField( + value = userPassword, + onValueChange = { userPassword = it }, + label = "Password", + hint = "Enter your password", + isPassword = true + ) + + Spacer(modifier = Modifier.height(20.dp)) + + SignUpTextField( + value = userNickname, + onValueChange = { userNickname = it }, + label = "Nickname", + hint = "Enter your nickname" + ) + + Spacer(modifier = Modifier.height(20.dp)) + + SignUpTextField( + value = userPhone, + onValueChange = { userPhone = it }, + label = "PHONE", + hint = "Enter your phone number" + ) + + Spacer(modifier = Modifier.height(30.dp)) + + Button(onClick = { + viewModel.signUp(userId, userPassword, userNickname, userPhone, context) + }) { + Text("Sign Up") + } + } +} + +@Composable +fun SignUpTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + hint: String, + isPassword: Boolean = false +) { + TextField( + value = value, + onValueChange = onValueChange, + label = { Text(label) }, + placeholder = { Text(hint) }, + singleLine = true, + modifier = Modifier + .fillMaxWidth(), + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None + ) +} + +class SignUpViewModel : ViewModel() { + private val _signUpState = mutableStateOf(SignUpState()) + fun signUp( + userId: String, + userPassword: String, + userNickname: String, + userPhone: String, + context: Context + ) { + val signUpRequestDto = RequestSignUpDto(userId, userPassword, userNickname, userPhone) + val authService = ServicePool.authService + + authService.signUp(signUpRequestDto).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val userId = response.headers()["location"] + _signUpState.value = SignUpState(isSuccess = true, message = "회원가입 성공!") + Log.e("userId", "sign up success for $userId") + + // 회원가입 성공 시 로그인 화면으로 이동 + val intent = Intent(context, LoginActivity::class.java) + context.startActivity(intent) + // 회원가입 성공 메시지 토스트로 띄우기 + Toast.makeText(context, "회원가입 성공!", Toast.LENGTH_SHORT).show() + } else { + val errorMessage = response.errorBody()?.string() ?: "Unknown error" + _signUpState.value = SignUpState(isSuccess = false, message = errorMessage) + // 회원가입 실패 시 실패 원인 토스트로 띄우기 + Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() + } + } + + override fun onFailure(call: Call, t: Throwable) { + val errorMessage = "서버 통신 오류: ${t.localizedMessage}" + _signUpState.value = SignUpState(isSuccess = false, message = errorMessage) + // 서버 통신 오류 시 토스트로 띄우기 + Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() + } + }) + } +} + +data class SignUpState( + val isSuccess: Boolean = false, + val message: String = "" +) diff --git a/app/src/main/java/com/sopt/now/compose/activity/SignupActivity.kt b/app/src/main/java/com/sopt/now/compose/activity/SignupActivity.kt deleted file mode 100644 index 6ab3950..0000000 --- a/app/src/main/java/com/sopt/now/compose/activity/SignupActivity.kt +++ /dev/null @@ -1,231 +0,0 @@ -package com.sopt.now.compose.activity - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -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 com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme - -class UserInfo( - val userId: String, - val userPassword: String, - val userNickname: String, - val userMbti: String -) - -@Composable -fun Signup () { - Column( - modifier = Modifier - .fillMaxSize() - .padding(30.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) - { - var userId by remember { mutableStateOf("") } - var userPassword by remember { mutableStateOf("") } - var userNickname by remember { mutableStateOf("") } - var userMbti by remember { mutableStateOf("") } - - Text( - text = "SIGN UP PAGE", - fontWeight = FontWeight.Bold, - fontSize = 30.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(30.dp)) - - Text( - text = "ID", - fontSize = 20.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Left - ) - - TextField( - value = userId, - onValueChange = { newValue -> userId = newValue }, - modifier = Modifier - .fillMaxWidth(), - label = { Text("아이디를 입력하세요(6~10자리)") }, - singleLine = true, - ) - - Spacer(modifier = Modifier.height(30.dp)) - - Text( - text = "PASSWORD", - fontSize = 20.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Left - ) - - TextField( - value = userPassword, - onValueChange = { newValue -> userPassword = newValue }, - modifier = Modifier - .fillMaxWidth(), - label = { Text("비밀번호를 입력하세요(8~12자리)") }, - singleLine = true, - ) - - Spacer(modifier = Modifier.height(30.dp)) - - Text( - text = "NICKNAME", - fontSize = 20.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Left, - ) - - TextField( - value = userNickname, - onValueChange = { newValue -> userNickname = newValue }, - modifier = Modifier - .fillMaxWidth(), - label = { Text("닉네임을 입력하세요(공백 안됨)") }, - singleLine = true, - ) - - Spacer(modifier = Modifier.height(30.dp)) - - Text( - text = "MBTI", - fontSize = 20.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Left, - ) - - TextField( - value = userMbti, - onValueChange = { newValue -> userMbti = newValue }, - modifier = Modifier - .fillMaxWidth(), - label = { Text("MBTI를 입력하세요") }, - singleLine = true, - ) - - Spacer(modifier = Modifier.height(30.dp)) - - val context = LocalContext.current - - Button(onClick = { - if (CheckId(context, userId) && CheckPassword(context, userPassword) && CheckNickname(context, userNickname) && CheckMbti(context, userMbti)) { - val intent = Intent(context, LoginActivity::class.java).apply { - putExtra("id", userId) - putExtra("password", userPassword) - putExtra("nickname", userNickname) - putExtra("mbti", userMbti) - } - context.startActivity(intent) // 회원가입 성공 시 MainActivity로 이동 - Toast.makeText(context, "회원가입 성공!", Toast.LENGTH_SHORT).show() - - } else { - - } - }) { - Text("Sign Up") - } - - } -} - -fun CheckId(context: Context, id: String): Boolean { - if (id.length >= 6 && id.length <= 10) { - return true - } else { - Toast.makeText(context, "ID는 6자 이상, 10자 이하로 입력해주세요", Toast.LENGTH_SHORT).show() - return false - } -} - -fun CheckPassword(context: Context, password: String): Boolean { - if(password.length >= 8 && password.length <= 12){ - return true - } - else { - Toast.makeText(context, "PASSWORD는 8자 이상, 12자 이하로 입력해주세요", Toast.LENGTH_SHORT).show() - return false - } -} - -fun CheckNickname(context: Context, nickname: String): Boolean { - if (nickname.isNotBlank() && nickname.length > 1) { - return true - } else { - Toast.makeText(context, "닉네임은 2자 이상의 유효한 문자로 입력해주세요", Toast.LENGTH_SHORT).show() - return false - } -} -fun CheckMbti(context: Context, mbti: String): Boolean { - if ((mbti.length == 4) - && (mbti[0].uppercase() == "E" || mbti[0].uppercase() == "I") - && (mbti[1].uppercase() == "S" || mbti[1].uppercase() == "N") - && (mbti[2].uppercase() == "F" || mbti[2].uppercase() == "T") - && (mbti[3].uppercase() == "P" || mbti[3].uppercase() == "J") - ) - return true - else { - Toast.makeText(context, "MBTI를 제대로 입력해주세요(ex. ENTJ)", Toast.LENGTH_SHORT).show() - return false - } -} -class SignupActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - NOWSOPTAndroidTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Signup() - } - } - } - } -} - - -@Preview(showBackground = true) -@Composable -fun SignupPreview() { - NOWSOPTAndroidTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ){ - Signup() - } - } -} diff --git a/app/src/main/java/com/sopt/now/compose/user/User.kt b/app/src/main/java/com/sopt/now/compose/user/User.kt index 860f06e..69aeeb8 100644 --- a/app/src/main/java/com/sopt/now/compose/user/User.kt +++ b/app/src/main/java/com/sopt/now/compose/user/User.kt @@ -1,4 +1,5 @@ package com.sopt.now.compose.user + import android.os.Parcel import android.os.Parcelable @@ -6,19 +7,20 @@ data class UserInfo( val id: String, val password: String, val nickname: String, - val mbti: String + val phone: String ) : Parcelable { constructor(parcel: Parcel) : this( parcel.readString() ?: "", parcel.readString() ?: "", parcel.readString() ?: "", - "ENTJ" + parcel.readString() ?: "" ) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(id) parcel.writeString(password) parcel.writeString(nickname) + parcel.writeString(phone) } override fun describeContents(): Int {