diff --git a/app/build.gradle b/app/build.gradle index 8bf8048..00bad4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,12 @@ 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' compileSdk 34 @@ -13,6 +17,7 @@ android { targetSdk 34 versionCode 1 versionName "1.0" + buildConfigField "String", "AUTH_BASE_URL", properties["base.url"] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -35,6 +40,8 @@ android { } buildFeatures { compose true + viewBinding true + buildConfig true } composeOptions { kotlinCompilerExtensionVersion '1.5.1' @@ -47,20 +54,39 @@ android { } dependencies { - implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.core:core-ktx:1.13.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 'androidx.activity:activity-compose:1.9.0' + implementation platform('androidx.compose:compose-bom:2024.04.01') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' implementation 'androidx.navigation:navigation-compose:2.7.7' + implementation 'com.android.support:support-annotations:28.0.0' + implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7' + implementation 'androidx.navigation:navigation-ui-ktx: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.01') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' + implementation "androidx.navigation:navigation-compose:2.7.7" + + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1' + 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:logging-interceptor") + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6e2d55e..51672cb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - - + + - - - \ No newline at end of file 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..359b9ae --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ApiFactory.kt @@ -0,0 +1,23 @@ +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..ac7d354 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/AuthService.kt @@ -0,0 +1,34 @@ +package com.sopt.now.compose + +import com.sopt.now.compose.data.RequestLogInDto +import com.sopt.now.compose.data.RequestPasswordDto +import com.sopt.now.compose.data.RequestSignUpDto +import com.sopt.now.compose.data.ResponseDto +import com.sopt.now.compose.data.ResponseInfoDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.PATCH +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 info(@Header("memberId") memberId: Int): Call + + @PATCH("member/password") + fun changePassword( + @Header("memberId") memberId: Int, + @Body request: RequestPasswordDto + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/BottomBar.kt b/app/src/main/java/com/sopt/now/compose/BottomBar.kt new file mode 100644 index 0000000..0d9636f --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/BottomBar.kt @@ -0,0 +1,85 @@ +package com.sopt.now.compose + +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.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState + + +@Composable +fun BottomBar( + navController: NavHostController, + modifier: Modifier = Modifier, + isLoggedIn: Boolean +) { + + val screens = listOf( + BottomNavItem.Home, BottomNavItem.Search, BottomNavItem.MyPage + ) + + if (isLoggedIn) { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + NavigationBar( + modifier = modifier, + containerColor = Color.LightGray, + ) { + screens.forEach { screen -> + + NavigationBarItem( + label = { + Text(text = screen.title!!) + }, + icon = { + Icon(imageVector = screen.icon!!, contentDescription = "") + }, + selected = currentRoute == screen.route, + onClick = { + navController.navigate(screen.route) + }, + colors = NavigationBarItemDefaults.colors( + unselectedTextColor = Color.Gray, selectedTextColor = Color.White + ), + ) + } + } + } + +} + +sealed class BottomNavItem( + val route: String, + val title: String? = null, + val icon: ImageVector? = null +) { + data object Home : BottomNavItem( + route = Routes.Home.route, + title = "Home", + icon = Icons.Default.Home + ) + + data object Search : BottomNavItem( + route = Routes.Search.route, + title = "Search", + icon = Icons.Default.Search + ) + + data object MyPage : BottomNavItem( + route = Routes.MyPage.route, + title = "MyPage", + icon = Icons.Default.Person + ) + +} diff --git a/app/src/main/java/com/sopt/now/compose/Friend.kt b/app/src/main/java/com/sopt/now/compose/Friend.kt new file mode 100644 index 0000000..8e69517 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/Friend.kt @@ -0,0 +1,8 @@ +package com.sopt.now.compose + +data class Friend( + val profileImageRes: Int, + val name: String, + val description: String + +) diff --git a/app/src/main/java/com/sopt/now/compose/LoginActicity.kt b/app/src/main/java/com/sopt/now/compose/LoginActicity.kt deleted file mode 100644 index d883691..0000000 --- a/app/src/main/java/com/sopt/now/compose/LoginActicity.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.sopt.now.compose - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.compose.setContent -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -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.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.unit.dp -import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme - -class LoginActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - NOWSOPTAndroidTheme { - LoginScreen() - } - } - } -} - - -@Composable -fun LoginScreen() { - var textid by remember { mutableStateOf("") } - var textpw by remember { mutableStateOf("") } - val context = LocalContext.current - - var userId by remember { mutableStateOf("") } - var userPw by remember { mutableStateOf("") } - var userNn by remember { mutableStateOf("") } - var userMBTI by remember { mutableStateOf("") } - - val nameId = "ID" - val namePassword = "PASSWORD" - val nameNickname = "NICKNAME" - val nameMbti = "MBTI" - - - val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val data = result.data - userId = data?.getStringExtra(nameId) ?: "" - userPw = data?.getStringExtra(namePassword) ?: "" - userNn = data?.getStringExtra(nameNickname) ?: "" - userMBTI = data?.getStringExtra(nameMbti) ?: "" - } else { - Toast.makeText(context, "데이터 못 받아옴", Toast.LENGTH_SHORT).show() - } - } - - - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(40.dp)) - Text(text = "Welcome to SOPT") - Spacer(modifier = Modifier.height(20.dp)) - TextField( - value = textid, - onValueChange = { textid = it }, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - label = { Text("사용자 이름 입력") }, - placeholder = { Text("") }, - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - keyboardActions = KeyboardActions( - onNext = { - } - ) - ) - TextField( - value = textpw, - onValueChange = { textpw = it }, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - label = { Text("비밀번호 입력") }, - placeholder = { Text("") }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) - ) - Spacer(modifier = Modifier.height(120.dp)) - - Button( - modifier = Modifier.padding(10.dp), - onClick = { - if (textid == userId && textpw == userPw && textid.isNotEmpty() && textpw.isNotEmpty()) { - Toast.makeText(context, "로그인 성공!", Toast.LENGTH_SHORT).show() - val intent = Intent(context, MainActivity::class.java) - intent.putExtra(nameId, textid) - intent.putExtra(namePassword, textpw) - intent.putExtra(nameNickname, userNn) - intent.putExtra(nameMbti, userMBTI) - (context as Activity).setResult(Activity.RESULT_OK,intent) - context.startActivity(intent) - context.finish() - } else { - Toast.makeText(context, "로그인 실패!", Toast.LENGTH_SHORT).show() - } - } - ) { - Text(text = "로그인 하기") - } - Button( - modifier = Modifier.padding(10.dp), - onClick = { launcher.launch(Intent(context, SignupActivity::class.java)) - } - ) { - Text(text = "회원가입 하기") - } - } -} 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 d93b0b4..53c49bb 100644 --- a/app/src/main/java/com/sopt/now/compose/MainActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/MainActivity.kt @@ -3,114 +3,45 @@ package com.sopt.now.compose import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp +import androidx.navigation.compose.rememberNavController import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme - class MainActivity : ComponentActivity() { - private val nameId = "ID" - private val namePassword = "PASSWORD" - private val nameNickname = "NICKNAME" - private val nameMbti = "MBTI" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { NOWSOPTAndroidTheme { - val userId = intent.getStringExtra(nameId) ?: "DefaultUserID" - val userPw = intent.getStringExtra(namePassword) ?: "DefaultPassword" - val userNn = intent.getStringExtra(nameNickname) ?: "DefaultNickname" - val userMBTI = intent.getStringExtra(nameMbti) ?: "DefaultMBTI" - MainScreen(userId, userPw, userNn, userMBTI) + val navController = rememberNavController() + var isLoggedIn by remember { mutableStateOf(false) } + + Scaffold( + bottomBar = { + if (isLoggedIn) { + BottomBar(navController = navController, isLoggedIn = true) + } + } + ) { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize() + ) { + NaviGraph(navController) { loggedIn -> + isLoggedIn = loggedIn + } + } + } } } } } - - -@Composable -fun MainScreen(userId: String, userPw: String, userNn: String, userMBTI: String) { - val imageResource = when (userMBTI.uppercase()) { - "ENFJ" -> R.drawable.enfj - "ENFP" -> R.drawable.enfp - "ENTJ" -> R.drawable.entj - "ENTP" -> R.drawable.entp - "ESFJ" -> R.drawable.esfj - "ESFP" -> R.drawable.esfp - "ESTJ" -> R.drawable.estj - "ESTP" -> R.drawable.estp - "INFJ" -> R.drawable.infj - "INFP" -> R.drawable.infp - "INTJ" -> R.drawable.intj - "INTP" -> R.drawable.intp - "ISFJ" -> R.drawable.isfj - "ISFP" -> R.drawable.isfp - "ISTJ" -> R.drawable.istj - "ISTP" -> R.drawable.istp - else -> R.drawable.ic_launcher_foreground // Use a default image if userMBTI doesn't match any specific case - } - - val textResource = when (userMBTI.uppercase()) { - "ENFJ" -> R.string.enfj - "ENFP" -> R.string.enfp - "ENTJ" -> R.string.entj - "ENTP" -> R.string.entp - "ESFJ" -> R.string.esfj - "ESFP" -> R.string.esfp - "ESTJ" -> R.string.estj - "ESTP" -> R.string.estp - "INFJ" -> R.string.infj - "INFP" -> R.string.infp - "INTJ" -> R.string.intj - "INTP" -> R.string.intp - "ISFJ" -> R.string.isfj - "ISFP" -> R.string.isfp - "ISTJ" -> R.string.istj - "ISTP" -> R.string.istp - else -> R.string.error // Use a default text resource if userMBTI doesn't match any specific case - } - - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(100.dp)) - Row { - Image( - painter = painterResource(id = R.drawable.ic_launcher_foreground), // Replace R.drawable.ic_launcher_foreground with your image resource - contentDescription = "프로필 이미지", - modifier = Modifier.background(Color.LightGray) - ) - Column(modifier = Modifier.padding(16.dp)) { - Text(text = "닉네임: $userNn") - Text(text = "ID : $userId") - Text(text = "Password: $userPw") - } - } - Spacer(modifier = Modifier.height(20.dp)) - Image( - painter = painterResource(id = imageResource), - contentDescription = "MBTI 이미지", - modifier = Modifier - .padding(16.dp) - .aspectRatio(16f / 9f) - - ) - Text(text = "MBTI: ${userMBTI.uppercase()}") - Text(text = stringResource(id = textResource), - modifier = Modifier.padding(20.dp)) - - } -} - - diff --git a/app/src/main/java/com/sopt/now/compose/MyProfile.kt b/app/src/main/java/com/sopt/now/compose/MyProfile.kt new file mode 100644 index 0000000..c69d593 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/MyProfile.kt @@ -0,0 +1,9 @@ +package com.sopt.now.compose + +data class MyProfile( + val profileImageRes: Int, + val name: String, + val description: String + +) + diff --git a/app/src/main/java/com/sopt/now/compose/NavViewModel.kt b/app/src/main/java/com/sopt/now/compose/NavViewModel.kt new file mode 100644 index 0000000..c74fd79 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/NavViewModel.kt @@ -0,0 +1,26 @@ +package com.sopt.now.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner + +@Composable +fun rememberViewModelStoreOwner(): ViewModelStoreOwner { + val context = LocalContext.current + return remember(context) { context as ViewModelStoreOwner } +} + +val LocalNavGraphViewModelStoreOwner = staticCompositionLocalOf { + error("Undefined") +} + +class NavViewModel : ViewModel() { + var memberId by mutableIntStateOf(-1) +} + diff --git a/app/src/main/java/com/sopt/now/compose/NaviGraph.kt b/app/src/main/java/com/sopt/now/compose/NaviGraph.kt new file mode 100644 index 0000000..640a7e6 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/NaviGraph.kt @@ -0,0 +1,68 @@ +package com.sopt.now.compose + + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.sopt.now.compose.screen.ChangePasswordScreen +import com.sopt.now.compose.screen.HomeScreen +import com.sopt.now.compose.screen.LoginScreen +import com.sopt.now.compose.screen.MyPageScreen +import com.sopt.now.compose.screen.SearchScreen +import com.sopt.now.compose.screen.SignupScreen + +sealed class Routes(val route: String) { + data object Login : Routes("Login") + data object SignUp : Routes("SignUp") + data object MyPage : Routes("MyPage") + data object Home : Routes("Home") + data object Search : Routes("Search") + data object Password : Routes("Password") + +} + +@Composable +fun NaviGraph( + navController: NavHostController, onLoginSuccess: (Boolean) -> Unit +) { + val navStoreOwner = rememberViewModelStoreOwner() + CompositionLocalProvider( + LocalNavGraphViewModelStoreOwner provides navStoreOwner + ) { + NavHost(navController = navController, startDestination = Routes.Login.route) { + + composable(route = Routes.Home.route) { + HomeScreen() + } + composable(route = Routes.Search.route) { + SearchScreen() + } + + composable( + route = Routes.MyPage.route, + ) { + MyPageScreen(navController = navController, onLoginSuccess = onLoginSuccess) + } + + composable( + route = Routes.Password.route, + ) { + ChangePasswordScreen(navController = navController) + } + + composable( + route = Routes.Login.route, + ) { + LoginScreen( + navController = navController, onLoginSuccess = onLoginSuccess + ) + } + + composable(route = Routes.SignUp.route) { + SignupScreen(navController = navController) + } + } + } +} diff --git a/app/src/main/java/com/sopt/now/compose/SignupActivity.kt b/app/src/main/java/com/sopt/now/compose/SignupActivity.kt deleted file mode 100644 index 4abe300..0000000 --- a/app/src/main/java/com/sopt/now/compose/SignupActivity.kt +++ /dev/null @@ -1,142 +0,0 @@ -package com.sopt.now.compose - -import android.app.Activity -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.* -import androidx.compose.foundation.text.KeyboardOptions -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.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.unit.dp -import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme - -class SignupActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - NOWSOPTAndroidTheme { - SignupScreen() - } - } - } -} - - -@Composable -fun SignupScreen() { - var textid by remember { mutableStateOf("") } - var textpw by remember { mutableStateOf("") } - var textnn by remember { mutableStateOf("") } - var textmbti by remember { mutableStateOf("") } - val context = LocalContext.current - - val nameId = "ID" - val namePassword = "PASSWORD" - val nameNickname = "NICKNAME" - val nameMbti = "MBTI" - - - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(40.dp)) - Text(text = "Sign Up") - Spacer(modifier = Modifier.height(20.dp)) - TextField( - value = textid, - onValueChange = { textid = it }, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - label = { Text("아이디를 입력해주세요.") }, - placeholder = { Text("") }, - singleLine = true, - ) - Spacer(modifier = Modifier.height(20.dp)) - TextField( - value = textpw, - onValueChange = { textpw = it }, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - label = { Text("비밀번호를 입력해주세요") }, - placeholder = { Text("") }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) - ) - Spacer(modifier = Modifier.height(20.dp)) - TextField( - value = textnn, - onValueChange = { textnn = it }, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - label = { Text("닉네임을 입력해주세요") }, - placeholder = { Text("") }, - singleLine = true - ) - Spacer(modifier = Modifier.height(20.dp)) - TextField( - value = textmbti, - onValueChange = { textmbti = it }, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - label = { Text("MBTI를 입력해주세요") }, - placeholder = { Text("") }, - singleLine = true - ) - Spacer(modifier = Modifier.height(120.dp)) - Button( - modifier = Modifier.padding(10.dp), - onClick = { - if (textid.length in 6..10) { - if (textpw.length in 8..12) { - if (textnn.trim().isNotEmpty()) { - if (isValidMBTIFormat(textmbti)) { - Toast.makeText(context, "회원가입 성공!", Toast.LENGTH_SHORT).show() - val intent = Intent(context, LoginActivity::class.java) - intent.putExtra(nameId, textid) - intent.putExtra(namePassword, textpw) - intent.putExtra(nameNickname, textnn) - intent.putExtra(nameMbti, textmbti) - (context as Activity).setResult(Activity.RESULT_OK,intent) - context.finish() - - } else { - Toast.makeText(context, "MBTI의 형식을 확인해주세요.", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(context, "닉네임은 한 글자 이상이어야 합니다.", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(context, "비밀번호는 8~12글자여야 합니다.", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(context, "아이디는 6~10글자여야 합니다.", Toast.LENGTH_SHORT).show() - } - - } - ) { - Text(text = "회원가입 하기") - } - } -} - -private fun isValidMBTIFormat(mbti: String): Boolean { - val validMBTIRegex = Regex("[EI][NS][FT][JP]") - return mbti.uppercase().matches(validMBTIRegex) -} - diff --git a/app/src/main/java/com/sopt/now/compose/data/RequestLogInDto.kt b/app/src/main/java/com/sopt/now/compose/data/RequestLogInDto.kt new file mode 100644 index 0000000..34730b8 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/RequestLogInDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data + +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/data/RequestPasswordDto.kt b/app/src/main/java/com/sopt/now/compose/data/RequestPasswordDto.kt new file mode 100644 index 0000000..0ef5122 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/RequestPasswordDto.kt @@ -0,0 +1,14 @@ +package com.sopt.now.compose.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestPasswordDto( + @SerialName("previousPassword") + val previousPassword: String, + @SerialName("newPassword") + val newPassword: String, + @SerialName("newPasswordVerification") + val newPasswordVerification: String +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/RequestSignUpDto.kt b/app/src/main/java/com/sopt/now/compose/data/RequestSignUpDto.kt new file mode 100644 index 0000000..065eec2 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/RequestSignUpDto.kt @@ -0,0 +1,16 @@ +package com.sopt.now.compose.data + +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/data/ResponseDto.kt b/app/src/main/java/com/sopt/now/compose/data/ResponseDto.kt new file mode 100644 index 0000000..c995885 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/ResponseDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseDto( + @SerialName("code") + val status: Int, + @SerialName("message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/ResponseInfoDto.kt b/app/src/main/java/com/sopt/now/compose/data/ResponseInfoDto.kt new file mode 100644 index 0000000..950959f --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/ResponseInfoDto.kt @@ -0,0 +1,21 @@ +package com.sopt.now.compose.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserInfo( + @SerialName("authenticationId") val authenticationId: String, + @SerialName("nickname") val nickname: String, + @SerialName("phone") val phone: String +) + +@Serializable +data class ResponseInfoDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, + @SerialName("data") + val data: UserInfo, +) diff --git a/app/src/main/java/com/sopt/now/compose/screen/ChangePasswordScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/ChangePasswordScreen.kt new file mode 100644 index 0000000..b2fed04 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/ChangePasswordScreen.kt @@ -0,0 +1,167 @@ +package com.sopt.now.compose.screen + +import android.widget.Toast +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.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +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.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.sopt.now.compose.LocalNavGraphViewModelStoreOwner +import com.sopt.now.compose.NavViewModel +import com.sopt.now.compose.Routes +import com.sopt.now.compose.ServicePool +import com.sopt.now.compose.data.RequestPasswordDto +import com.sopt.now.compose.data.ResponseDto +import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +@Composable +fun ChangePasswordScreen( + navController: NavHostController, +) { + + var previousPassword by remember { + mutableStateOf("") + } + var newPassword by remember { + mutableStateOf("") + } + var newPasswordCheck by remember { + mutableStateOf("") + } + + val navViewModel: NavViewModel = + viewModel(viewModelStoreOwner = LocalNavGraphViewModelStoreOwner.current) + val authService by lazy { ServicePool.authService } + + val context = LocalContext.current + + fun getPasswordRequestDto(): RequestPasswordDto { + val previousPw = previousPassword + val newPw = newPassword + val newPwCheck = newPasswordCheck + + return RequestPasswordDto( + previousPassword = previousPw, + newPassword = newPw, + newPasswordVerification = newPwCheck, + + ) + } + + fun changePassword(memberId: Int) { + val passwordRequest = getPasswordRequestDto() + authService.changePassword(memberId, passwordRequest) + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + Toast.makeText( + context, + "비밀번호 변경 완료! 새 비밀번호로 로그인 하세요", + Toast.LENGTH_SHORT + ).show() + navController.navigate(Routes.Login.route) { + popUpTo(Routes.Login.route) { + inclusive = true + } + launchSingleTop = true + } + } else { + Toast.makeText(context, "비밀번호 변경 실패", Toast.LENGTH_SHORT).show() + } + } + + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText(context, "비밀번호 변경 요청 실패: ${t.message}", Toast.LENGTH_SHORT) + .show() + } + }) + } + + + NOWSOPTAndroidTheme { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(40.dp)) + Text(text = "Welcome to SOPT") + Spacer(modifier = Modifier.height(20.dp)) + TextField( + value = previousPassword, + onValueChange = { previousPassword = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("기존 비밀번호 입력") }, + placeholder = { Text("") }, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { + } + ) + ) + TextField( + value = newPassword, + onValueChange = { newPassword = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("새 비밀번호 입력") }, + placeholder = { Text("") }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) + ) + + TextField( + value = newPasswordCheck, + onValueChange = { newPasswordCheck = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("새 비밀번호 확인") }, + placeholder = { Text("") }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) + ) + + Button( + modifier = Modifier.padding(10.dp), + onClick = { + changePassword(navViewModel.memberId) + } + ) { + Text(text = "비밀번호 변경") + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/screen/HomeScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/HomeScreen.kt new file mode 100644 index 0000000..a6eb3d0 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/HomeScreen.kt @@ -0,0 +1,263 @@ +package com.sopt.now.compose.screen + +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.Image +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +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.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.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.sopt.now.compose.Friend +import com.sopt.now.compose.LocalNavGraphViewModelStoreOwner +import com.sopt.now.compose.MyProfile +import com.sopt.now.compose.NavViewModel +import com.sopt.now.compose.R +import com.sopt.now.compose.ServicePool.authService +import com.sopt.now.compose.data.ResponseInfoDto +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + + +@Composable +fun HomeScreen() { + + val navViewModel: NavViewModel = + viewModel(viewModelStoreOwner = LocalNavGraphViewModelStoreOwner.current) + + val myImage = R.drawable.baseline_person_24 + var myName by remember { + mutableStateOf("") + } + val ybDescription = stringResource(id = R.string.yb) + val obDescription = stringResource(id = R.string.ob) + val partDescription = stringResource(id = R.string.part) + val friendImage = R.drawable.baseline_person_outline_24 + + val context = LocalContext.current + + fun searchInfo(memberId: Int) { + authService.info(memberId).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + myName = response.body()!!.data.nickname + } else { + Log.d("HomeActivity", "response ${response.body()?.message}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText(context, "조회 요청 실패: ${t.message}", Toast.LENGTH_SHORT) + .show() + } + }) + } + LaunchedEffect(Unit) { + searchInfo(navViewModel.memberId) + } + + val friendList = listOf( + Friend( + profileImageRes = friendImage, + name = "강문수", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "공세영", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "김명석", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "김아린", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "김언지", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "김윤서", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "박동민", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "박유진", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "배지현", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "배찬우", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "손민재", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "송혜음", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "우상욱", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "유정현", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "윤서희", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "곽의진", + description = partDescription + ), Friend( + profileImageRes = friendImage, name = "이가을", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "이나경", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "이석준", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "이석찬", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "이연진", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "이유빈", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "임하늘", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "주효은", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "최준서", + description = obDescription + ), Friend( + profileImageRes = friendImage, name = "이현진", + description = ybDescription + ), Friend( + profileImageRes = friendImage, name = "박효빈", + description = ybDescription + ) + ) + + + val myProfile = MyProfile( + profileImageRes = myImage, + name = myName, + description = ybDescription + ) + + LazyColumn( + modifier = Modifier + .padding(vertical = 8.dp) + .fillMaxSize() + ) { + item { + MyProfileItem(myProfile) + } + + items(friendList) { friend -> + FriendProfileItem(friend) + } + } +} + +@Composable +fun MyProfileItem(myProfile: MyProfile) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth() + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = myProfile.profileImageRes), + contentDescription = null, + modifier = Modifier + .size(55.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = myProfile.name, + fontSize = 18.sp, + fontWeight = FontWeight.ExtraBold + ) + Text( + text = myProfile.description, + fontSize = 16.sp, + color = Color.DarkGray, + textAlign = TextAlign.End, + modifier = Modifier.fillMaxWidth() + ) + + } + Spacer(modifier = Modifier.height(8.dp)) + HorizontalDivider() + } +} + +@Composable +fun FriendProfileItem(friend: Friend) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth() + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = friend.profileImageRes), + contentDescription = null, + modifier = Modifier + .size(45.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = friend.name, + fontWeight = FontWeight.Bold, + color = Color.DarkGray, + ) + Text( + text = friend.description, + color = Color.DarkGray, + textAlign = TextAlign.End, + modifier = Modifier.fillMaxWidth() + ) + + } + Spacer(modifier = Modifier.height(8.dp)) + HorizontalDivider() + } +} diff --git a/app/src/main/java/com/sopt/now/compose/screen/LoginScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/LoginScreen.kt new file mode 100644 index 0000000..fa65988 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/LoginScreen.kt @@ -0,0 +1,152 @@ +package com.sopt.now.compose.screen + + +import android.util.Log +import android.widget.Toast +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.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +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.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.sopt.now.compose.LocalNavGraphViewModelStoreOwner +import com.sopt.now.compose.NavViewModel +import com.sopt.now.compose.Routes +import com.sopt.now.compose.ServicePool +import com.sopt.now.compose.data.RequestLogInDto +import com.sopt.now.compose.data.ResponseDto +import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +@Composable +fun LoginScreen( + navController: NavHostController, + onLoginSuccess: (Boolean) -> Unit +) { + + var textId by remember { mutableStateOf("") } + var textPw by remember { mutableStateOf("") } + val context = LocalContext.current + + + val navViewModel: NavViewModel = + viewModel(viewModelStoreOwner = LocalNavGraphViewModelStoreOwner.current) + val authService by lazy { ServicePool.authService } + + + fun login() { + val id = textId + val password = textPw + val loginRequest = RequestLogInDto(id, password) + + authService.logIn(loginRequest).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val memberId = response.headers()["Location"]!!.toInt() + Toast.makeText( + context, + "로그인 성공 memberID: $memberId", + Toast.LENGTH_SHORT + ).show() + onLoginSuccess(true) + navViewModel.memberId = memberId + navController.navigate(Routes.Home.route) { + popUpTo(Routes.Login.route) { + inclusive = true + } + launchSingleTop = true + } + Log.d("LoginActivity", "memberId of viewModel : $memberId") + + } else { + Toast.makeText(context, "로그인 실패", Toast.LENGTH_SHORT).show() + } + } + + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText(context, "로그인 요청 실패: ${t.message}", Toast.LENGTH_SHORT) + .show() + } + }) + } + + NOWSOPTAndroidTheme { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(40.dp)) + Text(text = "Welcome to SOPT") + Spacer(modifier = Modifier.height(20.dp)) + TextField( + value = textId, + onValueChange = { textId = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("사용자 이름 입력") }, + placeholder = { Text("") }, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { + } + ) + ) + TextField( + value = textPw, + onValueChange = { textPw = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("비밀번호 입력") }, + placeholder = { Text("") }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) + ) + Spacer(modifier = Modifier.height(120.dp)) + + Button( + modifier = Modifier.padding(10.dp), + onClick = { + login() + } + ) { + Text(text = "로그인 하기") + } + Button( + modifier = Modifier.padding(10.dp), + onClick = { + navController.navigate(Routes.SignUp.route) + } + ) { + Text(text = "회원가입 하기") + } + } + } +} 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..70ffc73 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/MyPageScreen.kt @@ -0,0 +1,118 @@ +package com.sopt.now.compose.screen + +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.sopt.now.compose.LocalNavGraphViewModelStoreOwner +import com.sopt.now.compose.NavViewModel +import com.sopt.now.compose.R +import com.sopt.now.compose.Routes +import com.sopt.now.compose.ServicePool +import com.sopt.now.compose.data.ResponseInfoDto +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +@Composable +fun MyPageScreen(navController: NavHostController, onLoginSuccess: (Boolean) -> Unit) { + val navViewModel: NavViewModel = + viewModel(viewModelStoreOwner = LocalNavGraphViewModelStoreOwner.current) + + val context = LocalContext.current + + var userId by remember { + mutableStateOf("") + } + var userNickname by remember { + mutableStateOf("") + } + var userPhone by remember { + mutableStateOf("") + } + + val authService by lazy { ServicePool.authService } + + + Log.d("MyPageScreen", "MyPageScreen start") + + fun searchInfo(memberId: Int) { + authService.info(memberId).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + userId = response.body()!!.data.authenticationId + userNickname = response.body()!!.data.nickname + userPhone = response.body()!!.data.phone + + } else { + Log.d("HomeActivity", "response ${response.body()?.message}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText(context, "조회 요청 실패: ${t.message}", Toast.LENGTH_SHORT) + .show() + } + }) + } + + LaunchedEffect(Unit) { + searchInfo(navViewModel.memberId) + } + + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(100.dp)) + + Row { + Image( + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = "프로필 이미지", + modifier = Modifier.background(Color.LightGray) + ) + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "닉네임: $userNickname") + Text(text = "ID : $userId") + Text(text = "Phone: $userPhone") + Spacer(modifier = Modifier.padding(20.dp)) + Button(onClick = { + onLoginSuccess(false) + navController.navigate(Routes.Password.route) + }) { + Text(text = "비밀번호 변경") + } + } + + } + + + } +} diff --git a/app/src/main/java/com/sopt/now/compose/screen/SearchScreen.kt b/app/src/main/java/com/sopt/now/compose/screen/SearchScreen.kt new file mode 100644 index 0000000..665b99a --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/SearchScreen.kt @@ -0,0 +1,27 @@ +package com.sopt.now.compose.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +@Composable +fun SearchScreen() { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Search Screen", + fontSize = 40.sp, + fontWeight = FontWeight.ExtraBold + ) + + } +} \ 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..d0c166f --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/screen/SignupScreen.kt @@ -0,0 +1,168 @@ +package com.sopt.now.compose.screen + +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +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.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.sopt.now.compose.Routes +import com.sopt.now.compose.ServicePool +import com.sopt.now.compose.data.RequestSignUpDto +import com.sopt.now.compose.data.ResponseDto +import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + + +@Composable +fun SignupScreen(navController: NavController) { + var textId by remember { mutableStateOf("") } + var textPw by remember { mutableStateOf("") } + var textNickname by remember { mutableStateOf("") } + var textPhone by remember { mutableStateOf("") } + val context = LocalContext.current + + + val authService by lazy { ServicePool.authService } + + fun getSignUpRequestDto(): RequestSignUpDto { + val id = textId + val password = textPw + val nickname = textNickname + val phoneNumber = textPhone + return RequestSignUpDto( + authenticationId = id, + password = password, + nickname = nickname, + phone = phoneNumber + ) + } + + + fun signUp() { + val signUpRequest = getSignUpRequestDto() + authService.signUp(signUpRequest).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val data: ResponseDto? = response.body() + val userId = response.headers()["location"] + Toast.makeText( + context, + "회원가입 성공 유저의 ID는 $userId 입니둥", + Toast.LENGTH_SHORT, + ).show() + Log.d("SignUp", "data: $data, userId: $userId") + navController.navigate(Routes.Login.route) + } else { + val errorC = response.code() + val errorM = response.message() + Toast.makeText( + context, + "회원가입 실패 $errorC , $errorM", + Toast.LENGTH_SHORT, + ).show() + } + } + + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText(context, "서버 에러 발생 ", Toast.LENGTH_SHORT).show() + } + }) + } + + + + + NOWSOPTAndroidTheme { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(40.dp)) + Text(text = "Sign Up") + Spacer(modifier = Modifier.height(20.dp)) + TextField( + value = textId, + onValueChange = { textId = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("아이디를 입력해주세요.") }, + placeholder = { Text("") }, + singleLine = true, + ) + Spacer(modifier = Modifier.height(20.dp)) + TextField( + value = textPw, + onValueChange = { textPw = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("비밀번호를 입력해주세요") }, + placeholder = { Text("") }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) + ) + Spacer(modifier = Modifier.height(20.dp)) + TextField( + value = textNickname, + onValueChange = { textNickname = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("닉네임을 입력해주세요") }, + placeholder = { Text("") }, + singleLine = true + ) + Spacer(modifier = Modifier.height(20.dp)) + TextField( + value = textPhone, + onValueChange = { textPhone = it }, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + label = { Text("전화번호를 입력해주세요") }, + placeholder = { Text("") }, + singleLine = true + ) + Spacer(modifier = Modifier.height(120.dp)) + Button( + modifier = Modifier.padding(10.dp), + onClick = { + signUp() + } + ) { + Text(text = "회원가입 하기") + } + } + } +} + +//private fun isValidMBTIFormat(mbti: String): Boolean { +// val validMBTIRegex = Regex("[EI][NS][FT][JP]") +// return mbti.uppercase().matches(validMBTIRegex) +//} + diff --git a/app/src/main/res/drawable/baseline_home_24.xml b/app/src/main/res/drawable/baseline_home_24.xml new file mode 100644 index 0000000..20cb4d6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_home_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_person_24.xml b/app/src/main/res/drawable/baseline_person_24.xml new file mode 100644 index 0000000..f4cc418 --- /dev/null +++ b/app/src/main/res/drawable/baseline_person_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_person_outline_24.xml b/app/src/main/res/drawable/baseline_person_outline_24.xml new file mode 100644 index 0000000..a3be1b8 --- /dev/null +++ b/app/src/main/res/drawable/baseline_person_outline_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_search_24.xml b/app/src/main/res/drawable/baseline_search_24.xml new file mode 100644 index 0000000..d29c6ea --- /dev/null +++ b/app/src/main/res/drawable/baseline_search_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..b258b88 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..22d7f00 --- /dev/null +++ b/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..c893428 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml new file mode 100644 index 0000000..4651d5c --- /dev/null +++ b/app/src/main/res/values-v23/themes.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 0000000..d73f4a3 --- /dev/null +++ b/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ + + 200dp + \ No newline at end of file diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml new file mode 100644 index 0000000..22d7f00 --- /dev/null +++ b/app/src/main/res/values-w600dp/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..125df87 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,3 @@ + + 16dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c3f23e..c5bf75e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,4 +17,13 @@ 풍부한 상상력으로 지적인 도전을 즐기며 뜨거운 논쟁을 즐기는 변론가 비전을 갖고 타인을 동기 부여하는 리더쉽을 지닌 대담한 통솔자 오류 + Home + Search + MyPage + 34기 안드로이드 YB + 34기 안드로이드 OB + 나는 34기 안드로이드 파트장이고 이건 한 줄 넘어갔을 때 어떻게 되는지 확인하려는 테스트용 텍스트야! + Your first fragment label + Your second fragment label + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 73b3902..15dc8f3 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -2,4 +2,9 @@ \ No newline at end of file