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