diff --git a/app/build.gradle b/app/build.gradle index f534bf6..35c39c6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,15 +50,17 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' implementation 'androidx.activity:activity-compose:1.8.2' - implementation platform('androidx.compose:compose-bom:2024.03.00') + implementation platform('androidx.compose:compose-bom:2024.04.00') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' + // Navigation + implementation 'androidx.navigation:navigation-compose:2.7.7' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation platform('androidx.compose:compose-bom:2024.03.00') + androidTestImplementation platform('androidx.compose:compose-bom:2024.04.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9d2cd40..a9fa545 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,6 @@ diff --git a/app/src/main/java/com/sopt/now/compose/MainActivity.kt b/app/src/main/java/com/sopt/now/compose/MainActivity.kt index 73fb148..a2577b2 100644 --- a/app/src/main/java/com/sopt/now/compose/MainActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/MainActivity.kt @@ -3,44 +3,41 @@ package com.sopt.now.compose import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.sopt.now.compose.screen.MyPageScreen +import com.sopt.now.compose.screen.SignInScreen +import com.sopt.now.compose.screen.SignUpScreen class MainActivity : 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 - ) { - Greeting("Android") + val navController = rememberNavController() + + NavHost(navController = navController, startDestination = "SignIn") { + composable("SignIn?ID={ID}&PW={PW}&Name={Name}&Place={Place}") { backStackEntry -> + SignInScreen( + navController = navController, + ID = backStackEntry.arguments?.getString("ID") ?: "", + PW = backStackEntry.arguments?.getString("PW") ?: "", + Name = backStackEntry.arguments?.getString("Name") ?: "", + Place = backStackEntry.arguments?.getString("Place") ?: "" + ) + } + composable("SignUp") { + SignUpScreen(navController = navController) + } + composable("MyPage?ID={InputID}&PW={InputPW}&Name={Name}&Place={Place}") { backStackEntry -> + MyPageScreen( + ID = backStackEntry.arguments?.getString("InputID") ?: "", + PW = backStackEntry.arguments?.getString("InputPW") ?: "", + Name = backStackEntry.arguments?.getString("Name") ?: "", + Place = backStackEntry.arguments?.getString("Place") ?: "" + ) } } } } -} - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - NOWSOPTAndroidTheme { - Greeting("Android") - } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/Sources.kt b/app/src/main/java/com/sopt/now/compose/Sources.kt new file mode 100644 index 0000000..f0a3471 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Sources.kt @@ -0,0 +1,35 @@ +package com.sopt.now.compose + +import android.content.Context +import android.widget.Toast +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +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 androidx.navigation.NavController + + +@Composable +fun SOPTOutlinedButton(text: Int, onClick: () -> Unit, enabled: Boolean) { + OutlinedButton( + onClick = onClick, modifier = Modifier.fillMaxWidth(), enabled = enabled + ) { + Text(text = stringResource(id = text)) + } +} + +fun showToast(context: Context, message: Int) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/screen/MyPageScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/MyPageScreen.kt new file mode 100644 index 0000000..5b4027e --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/MyPageScreen.kt @@ -0,0 +1,60 @@ +package com.sopt.now.compose.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sopt.now.compose.R + +@Composable +fun MyPageScreen(ID: String, PW: String, Name: String, Place: String) { + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 30.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + + + Image(painter = painterResource(id = R.drawable.img_mypage_profile), + contentDescription = "Profile", + modifier = Modifier.size(300.dp) + ) + + MyPageText(text = stringResource(R.string.txt_MyPage_Title)) + MyPageText(text = Name) + MyPageText(text = stringResource(R.string.txt_MyPage_Id)) + MyPageText(text = ID) + MyPageText(text = stringResource(R.string.txt_MyPage_Pw)) + MyPageText(text = PW) + MyPageText(text = stringResource(R.string.txt_MyPage_Place)) + MyPageText(text = Place) + } +} + +@Composable +fun MyPageText( + text: String +) { + Text( + text = text, + modifier = Modifier.padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/screen/SignInScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/SignInScreen.kt new file mode 100644 index 0000000..7eb6c27 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/SignInScreen.kt @@ -0,0 +1,142 @@ +package com.sopt.now.compose.screen + +import android.content.Context +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.Icon +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.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +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 androidx.navigation.NavController +import com.sopt.now.compose.R +import com.sopt.now.compose.SOPTOutlinedButton +import com.sopt.now.compose.showToast + +@Composable +fun SignInScreen( + navController: NavController, ID: String, PW: String, Name: String, Place: String +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 30.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + var inputID by remember { mutableStateOf("") } + var inputPW by remember { mutableStateOf("") } + val context = LocalContext.current + + Text( + text = stringResource(id = R.string.txt_SignIn_Title), + modifier = Modifier.padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + + SignInTextField( + inputID, + { inputID = it }, + stringResource(id = R.string.tf_SignIn_ID_Hint), + Icons.Filled.Person + ) + + SignInTextField( + inputPW, + { inputPW = it }, + stringResource(id = R.string.tf_SignIn_PW_Hint), + Icons.Filled.Lock, + isPassword = true + ) + + SOPTOutlinedButton( + R.string.btn_SignIn_SignIn, + { isSignInValid(navController, context, inputID, inputPW, ID, PW, Name, Place) }, + true + ) + + SOPTOutlinedButton( + R.string.btn_SignIn_SignUp, + { navController.navigate("SignUp") }, + true + ) + } +} + +@Composable +fun SignInTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + leadingIcon: ImageVector, + modifier: Modifier = Modifier, + isPassword: Boolean = false, +) { + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text(label) }, + leadingIcon = { Icon(leadingIcon, contentDescription = null) }, + singleLine = true, + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None + ) +} + +fun isSignInValid( + navController: NavController, + context: Context, + inputID: String, + inputPW: String, + ID: String, + PW: String, + Name: String, + Place: String +) { + when { + (inputID.isEmpty()) -> { + showToast(context, R.string.toast_SignIn_InvalidSignIn_IDBlank) + } + + (inputPW.isEmpty()) -> { + showToast(context, R.string.toast_SignIn_InvalidSignIn_PWBlank) + } + + (inputID == ID && inputPW == PW) -> { + showToast(context, R.string.toast_SignIn_ValidSignIn) + navController.navigate("MyPage?ID=$inputID&PW=$inputPW&Name=$Name&Place=$Place") + } + + (inputID != ID) -> { + showToast(context, R.string.toast_SignIn_InvalidID) + } + + (inputPW != PW) -> { + showToast(context, R.string.toast_SignIn_InvalidPW) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/screen/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/SignUpScreen.kt new file mode 100644 index 0000000..b22b7ba --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/SignUpScreen.kt @@ -0,0 +1,175 @@ +package com.sopt.now.compose.screen + +import android.content.Context +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Face +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.Icon +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.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +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 androidx.navigation.NavController +import com.sopt.now.compose.R +import com.sopt.now.compose.SOPTOutlinedButton +import com.sopt.now.compose.showToast + +@Composable +fun SignUpScreen(navController: NavController) { + val (value, setValue) = remember { + mutableStateOf("") + } + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 30.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + + var ID by remember { mutableStateOf("") } + var PW by remember { mutableStateOf("") } + var Name by remember { mutableStateOf("") } + var Place by remember { mutableStateOf("") } + var isIDValid by remember { mutableStateOf(false) } + var isPWValid by remember { mutableStateOf(false) } + var isNameValid by remember { mutableStateOf(false) } + val context = LocalContext.current + + val MIN_ID_LENGTH = 6 + val MAX_ID_LENGTH = 10 + val MIN_PW_LENGTH = 8 + val MAX_PW_LENGTH = 12 + + // Title + Text( + text = stringResource(R.string.txt_SignUp_Title), + modifier = Modifier.padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + + SignUpTextField( + ID, + { newID -> + ID = newID + isIDValid = newID.trim().length in MIN_ID_LENGTH..MAX_ID_LENGTH + }, + stringResource(R.string.tf_SignUp_ID_Hint), + Icons.Filled.Person + ) + + SignUpTextField( + PW, + { newPW -> + PW = newPW + isPWValid = newPW.trim().length in MIN_PW_LENGTH..MAX_PW_LENGTH + }, + stringResource(R.string.tf_SignUp_PW_Hint), + Icons.Filled.Lock, + isPassword = true + ) + + SignUpTextField( + Name, + { newName -> + Name = newName + isNameValid = newName.trim().isNotEmpty() + }, + stringResource(R.string.tf_SignUp_Name_Hint), + Icons.Filled.Face + ) + + SignUpTextField( + Place, + { newPlace -> Place = newPlace }, + stringResource(R.string.tf_SignUp_Place_Hint), + Icons.Filled.Home + ) + + SOPTOutlinedButton( + R.string.btn_SignUp_SignUp, + { + isSignUpValid( + navController, context, + ID, isIDValid, + PW, isPWValid, + Name, isNameValid, Place + ) + }, + true + ) + } +} + +@Composable +fun SignUpTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + leadingIcon: ImageVector?, + modifier: Modifier = Modifier, + isPassword: Boolean = false +) { + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text(label) }, + leadingIcon = if (leadingIcon != null) { + { Icon(leadingIcon, contentDescription = null) } + } else { + null + }, + singleLine = true, + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None + ) +} + +fun isSignUpValid( + navController: NavController, + context: Context, + ID: String, + IDValid: Boolean, + PW: String, + PWValid: Boolean, + Name: String, + NameValid: Boolean, + Place: String +) { + if (ID.isEmpty()||PW.isEmpty()|| + Name.isEmpty()||Place.isEmpty()) + showToast(context, R.string.toast_SignUp_InvalidSignUp_Blank) + + else if (IDValid && PWValid && NameValid) { + showToast(context, R.string.toast_SignUp_ValidSignUp) + navController.navigate("SignIn?ID=$ID&PW=$PW&Name=$Name&Place=$Place") + } + + else + showToast(context,R.string.toast_SignUp_InvalidSignUp) +} diff --git a/app/src/main/res/drawable-nodpi/img_mypage_profile.jpg b/app/src/main/res/drawable-nodpi/img_mypage_profile.jpg new file mode 100644 index 0000000..2f55c3c Binary files /dev/null and b/app/src/main/res/drawable-nodpi/img_mypage_profile.jpg differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be0d381..d86d5cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,32 @@ NOW SOPT Android + + + Welcome to SOPT! + 아이디를 입력하세요 + 비밀번호를 입력하세요 + 로그인 하기 + 회원가입 하기 + 로그인 성공 + 아이디가 잘못되었습니다 + 비밀번호가 잘못되었습니다 + 아이디를 입력해주세요 + 비밀번호를 입력해주세요 + + + SIGN UP + 아이디를 입력하세요 (6~10자) + 비밀번호를 입력하세요(8~12자) + 닉네임을 입력하세요 + 거주지를 입력하세요 + 회원가입 하기 + 회원가입 성공 + 회원가입 실패 + 입력하지 않은 정보가 있습니다 + + + SOPT에 온걸 환영해! + 아이디 + 비밀번호 + 거주지 \ No newline at end of file