From 4c4a3418025dcac0c903ae9f7ac0ee4c4867f192 Mon Sep 17 00:00:00 2001 From: MinseoSong Date: Wed, 13 Nov 2024 18:53:45 +0900 Subject: [PATCH 1/7] =?UTF-8?q?#7=20=EC=84=B8=ED=8C=85=20-=20base=20url=20?= =?UTF-8?q?=EC=88=A8=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 8 +++++ .../org/sopt/and/component/BuyTextButton.kt | 2 +- .../java/org/sopt/and/component/MyPageItem.kt | 2 +- .../org/sopt/and/component/bar/BottomBar.kt | 5 ++- .../java/org/sopt/and/component/bar/TopBar.kt | 2 +- .../and/component/textField/IDTextfield.kt | 2 +- .../java/org/sopt/and/navigate/NavGraph.kt | 31 +++++++++++++++---- .../main/java/org/sopt/and/ui/my/MyScreen.kt | 4 +-- .../org/sopt/and/ui/search/SearchScreen.kt | 3 -- .../org/sopt/and/ui/signup/SignUpScreen.kt | 21 ++++++++----- .../org/sopt/and/ui/signup/SignUpViewModel.kt | 5 --- 11 files changed, 53 insertions(+), 32 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd336bb..4879ad7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,9 +1,15 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) } +val properties = Properties().apply{ + load(project.rootProject.file("local.properties").inputStream()) +} + android { namespace = "org.sopt.and" compileSdk = 35 @@ -16,6 +22,7 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String","BASE_URL", properties["base.url"].toString()) } buildTypes { @@ -36,6 +43,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } diff --git a/app/src/main/java/org/sopt/and/component/BuyTextButton.kt b/app/src/main/java/org/sopt/and/component/BuyTextButton.kt index 7e63908..a432d77 100644 --- a/app/src/main/java/org/sopt/and/component/BuyTextButton.kt +++ b/app/src/main/java/org/sopt/and/component/BuyTextButton.kt @@ -18,7 +18,7 @@ import org.sopt.and.R @Composable fun BuyTextButton( labelText: String, - onClick: ()->Unit + onClick: () -> Unit ) { Column { Text( diff --git a/app/src/main/java/org/sopt/and/component/MyPageItem.kt b/app/src/main/java/org/sopt/and/component/MyPageItem.kt index 98c8f99..2fd6295 100644 --- a/app/src/main/java/org/sopt/and/component/MyPageItem.kt +++ b/app/src/main/java/org/sopt/and/component/MyPageItem.kt @@ -34,7 +34,7 @@ fun MyPageItem( .padding(bottom = 80.dp) .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally - ){ + ) { Image( painter = icon, contentDescription = "", diff --git a/app/src/main/java/org/sopt/and/component/bar/BottomBar.kt b/app/src/main/java/org/sopt/and/component/bar/BottomBar.kt index 5613dbb..2277238 100644 --- a/app/src/main/java/org/sopt/and/component/bar/BottomBar.kt +++ b/app/src/main/java/org/sopt/and/component/bar/BottomBar.kt @@ -24,7 +24,6 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController -import org.sopt.and.navigate.Routes import org.sopt.and.navigate.ScreenTab @Composable @@ -55,8 +54,8 @@ fun BottomBar( BottomTab( onClick = { - navController.navigate(tab.route){ - popUpTo(tab.route){ + navController.navigate(tab.route) { + popUpTo(tab.route) { inclusive = true } } diff --git a/app/src/main/java/org/sopt/and/component/bar/TopBar.kt b/app/src/main/java/org/sopt/and/component/bar/TopBar.kt index 8fdd774..026e0b8 100644 --- a/app/src/main/java/org/sopt/and/component/bar/TopBar.kt +++ b/app/src/main/java/org/sopt/and/component/bar/TopBar.kt @@ -27,7 +27,7 @@ fun TopBar(modifier: Modifier = Modifier) { modifier = Modifier.width(100.dp) ) - Row{ + Row { Image( painter = painterResource(R.drawable.outline_cast_connected_24), contentDescription = "", diff --git a/app/src/main/java/org/sopt/and/component/textField/IDTextfield.kt b/app/src/main/java/org/sopt/and/component/textField/IDTextfield.kt index 5922673..35d67e1 100644 --- a/app/src/main/java/org/sopt/and/component/textField/IDTextfield.kt +++ b/app/src/main/java/org/sopt/and/component/textField/IDTextfield.kt @@ -15,7 +15,7 @@ fun IDTextField( onValueChange: (String) -> Unit, placeholder: String, modifier: Modifier = Modifier -){ +) { TextField( value = value, onValueChange = onValueChange, diff --git a/app/src/main/java/org/sopt/and/navigate/NavGraph.kt b/app/src/main/java/org/sopt/and/navigate/NavGraph.kt index 3c982d0..61e6ace 100644 --- a/app/src/main/java/org/sopt/and/navigate/NavGraph.kt +++ b/app/src/main/java/org/sopt/and/navigate/NavGraph.kt @@ -16,7 +16,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import org.sopt.and.component.bar.BottomBar import org.sopt.and.ui.home.HomeScreen -import org.sopt.and.ui.home.HomeViewModel import org.sopt.and.ui.my.MyScreen import org.sopt.and.ui.search.SearchScreen import org.sopt.and.ui.signin.SignInScreen @@ -25,7 +24,7 @@ import org.sopt.and.ui.signup.SignUpScreen import org.sopt.and.ui.signup.SignUpViewModel @Composable -fun NavGraph(navController: NavHostController){ +fun NavGraph(navController: NavHostController) { val signUpViewModel: SignUpViewModel = viewModel() val signInViewModel: SignInViewModel = viewModel() @@ -37,7 +36,12 @@ fun NavGraph(navController: NavHostController){ val selectedScreen = ScreenTab.entries.find { it.route == currentRoute } if (selectedScreen != null) { BottomBar(selected = selectedScreen, navController = navController) - }else if (currentRoute in listOf(Routes.Home.route, Routes.My.route, Routes.Search.route)) { + } else if (currentRoute in listOf( + Routes.Home.route, + Routes.My.route, + Routes.Search.route + ) + ) { BottomBar(selected = null, navController = navController) } } @@ -49,9 +53,24 @@ fun NavGraph(navController: NavHostController){ .padding(paddingValues) ) { NavHost(navController = navController, startDestination = Routes.SignIn.route) { - composable(Routes.SignIn.route) { SignInScreen(navController, signInViewModel = signInViewModel) } - composable(Routes.SignUp.route) { SignUpScreen(navController, signUpViewModel = signUpViewModel) } - composable(Routes.My.route) { MyScreen(navController, signInViewModel = signInViewModel) } + composable(Routes.SignIn.route) { + SignInScreen( + navController, + signInViewModel = signInViewModel + ) + } + composable(Routes.SignUp.route) { + SignUpScreen( + navController, + signUpViewModel = signUpViewModel + ) + } + composable(Routes.My.route) { + MyScreen( + navController, + signInViewModel = signInViewModel + ) + } composable(Routes.Search.route) { SearchScreen(navController) } composable(Routes.Home.route) { HomeScreen(navController) } } diff --git a/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt b/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt index 6975c51..1717cd0 100644 --- a/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -25,7 +24,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import org.sopt.and.R -import org.sopt.and.component.bar.BottomBar import org.sopt.and.component.BuyTextButton import org.sopt.and.component.MyPageItem import org.sopt.and.ui.signin.SignInViewModel @@ -39,7 +37,7 @@ fun MyScreen( Column( modifier = Modifier.fillMaxSize() - ){ + ) { Column( modifier = Modifier .background(Color.DarkGray) diff --git a/app/src/main/java/org/sopt/and/ui/search/SearchScreen.kt b/app/src/main/java/org/sopt/and/ui/search/SearchScreen.kt index 3a5fb74..4b03395 100644 --- a/app/src/main/java/org/sopt/and/ui/search/SearchScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/search/SearchScreen.kt @@ -3,13 +3,10 @@ package org.sopt.and.ui.search import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.navigation.NavHostController -import org.sopt.and.component.bar.BottomBar @Composable fun SearchScreen( diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt index 0d4e589..9446b27 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt @@ -48,7 +48,7 @@ fun SignUpScreen( Column( modifier = modifier - ){ + ) { Box( modifier = Modifier .fillMaxSize() @@ -58,7 +58,7 @@ fun SignUpScreen( modifier = Modifier .padding(20.dp) .align(Alignment.TopStart) - ){ + ) { Text( text = stringResource(R.string.signup_text), color = Color.Gray @@ -67,7 +67,7 @@ fun SignUpScreen( IDTextField( value = userId, - onValueChange = {userId = it}, + onValueChange = { userId = it }, modifier = Modifier.fillMaxWidth(), placeholder = context.getString(R.string.signup_id) ) @@ -95,15 +95,20 @@ fun SignUpScreen( modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter) - ){ + ) { Button( onClick = { - if (signUpViewModel.isAbleEmail(userId) && signUpViewModel.isAblePassword(userPassWord)){ + if (signUpViewModel.isAbleEmail(userId) && signUpViewModel.isAblePassword( + userPassWord + ) + ) { signUpViewModel.saveUserInfo(userId, userPassWord) navController.popBackStack() - Toast.makeText(context, (R.string.signup_success),Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(context, (R.string.signup_fail),Toast.LENGTH_SHORT).show() + Toast.makeText(context, (R.string.signup_success), Toast.LENGTH_SHORT) + .show() + } else { + Toast.makeText(context, (R.string.signup_fail), Toast.LENGTH_SHORT) + .show() } }, modifier = Modifier diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt index a2728ef..5447424 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt @@ -2,11 +2,7 @@ package org.sopt.and.ui.signup import android.content.Context import android.content.SharedPreferences -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import org.sopt.and.model.UserInfo class SignUpViewModel: ViewModel() { @@ -15,7 +11,6 @@ class SignUpViewModel: ViewModel() { val PASSWORD_REGEX = Regex("^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@\$!%*?&])[A-Za-z\\d@\$!%*?&]{$PASSWORD_MIN_LENGTH,$PASSWORD_MAX_LENGTH}\$") var sharedPreferences: SharedPreferences? = null - var userInfo by mutableStateOf(UserInfo("","")) fun initializePreferences(context: Context){ sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) From f1f41110c3a8256c4a5df779743ae2f85697298d Mon Sep 17 00:00:00 2001 From: MinseoSong Date: Wed, 13 Nov 2024 18:59:14 +0900 Subject: [PATCH 2/7] =?UTF-8?q?#7=20=EC=84=B8=ED=8C=85=20-=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=EB=84=B7,=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 8 ++++++++ app/src/main/AndroidManifest.xml | 1 + gradle/libs.versions.toml | 11 +++++++++++ 3 files changed, 20 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4879ad7..0bfa735 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) } val properties = Properties().apply{ @@ -69,4 +70,11 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + // Network + implementation(platform(libs.okhttp.bom)) + implementation(libs.okhttp) + implementation(libs.okhttp.logging.interceptor) + implementation(libs.retrofit) + implementation(libs.retrofit.kotlin.serialization.converter) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3f3d5d4..a185455 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ANDANDROID" + android:usesCleartextTraffic="true" tools:targetApi="31"> Date: Wed, 13 Nov 2024 20:08:46 +0900 Subject: [PATCH 3/7] =?UTF-8?q?#7=20=EC=84=9C=EB=B2=84=EC=97=B0=EB=8F=99?= =?UTF-8?q?=20-=20=EC=9C=A0=EC=A0=80=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/api/ApiFactory.kt | 36 +++++++ .../java/org/sopt/and/api/dto/ErrorDTO.kt | 10 ++ .../main/java/org/sopt/and/api/dto/UserDTO.kt | 26 +++++ .../org/sopt/and/api/service/UserService.kt | 14 +++ .../org/sopt/and/ui/signup/SignUpScreen.kt | 42 +++++--- .../org/sopt/and/ui/signup/SignUpViewModel.kt | 97 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 7 files changed, 191 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/api/ApiFactory.kt create mode 100644 app/src/main/java/org/sopt/and/api/dto/ErrorDTO.kt create mode 100644 app/src/main/java/org/sopt/and/api/dto/UserDTO.kt create mode 100644 app/src/main/java/org/sopt/and/api/service/UserService.kt diff --git a/app/src/main/java/org/sopt/and/api/ApiFactory.kt b/app/src/main/java/org/sopt/and/api/ApiFactory.kt new file mode 100644 index 0000000..8fb713a --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/ApiFactory.kt @@ -0,0 +1,36 @@ +package org.sopt.and.api + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.sopt.and.BuildConfig +import org.sopt.and.api.service.UserService +import retrofit2.Retrofit + +object ApiFactory { + private const val BASE_URL: String = BuildConfig.BASE_URL + + private val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + private val client = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + + val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + inline fun create(): T = retrofit.create(T::class.java) +} + +object ServicePool { + val userService = ApiFactory.create() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/dto/ErrorDTO.kt b/app/src/main/java/org/sopt/and/api/dto/ErrorDTO.kt new file mode 100644 index 0000000..7739cf9 --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/dto/ErrorDTO.kt @@ -0,0 +1,10 @@ +package org.sopt.and.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseErrorDto( + @SerialName("code") + val code: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/dto/UserDTO.kt b/app/src/main/java/org/sopt/and/api/dto/UserDTO.kt new file mode 100644 index 0000000..f46daf1 --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/dto/UserDTO.kt @@ -0,0 +1,26 @@ +package org.sopt.and.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseUserSuccessDto( + @SerialName("result") + val result: UserData +) + +@Serializable +data class UserData( + @SerialName("no") + val no: Int +) + +@Serializable +data class RequestUserDto( + @SerialName("username") + val username: String, + @SerialName("password") + val password: String, + @SerialName("hobby") + val hobby: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/service/UserService.kt b/app/src/main/java/org/sopt/and/api/service/UserService.kt new file mode 100644 index 0000000..2e20508 --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/service/UserService.kt @@ -0,0 +1,14 @@ +package org.sopt.and.api.service + +import okhttp3.ResponseBody +import org.sopt.and.api.dto.RequestUserDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface UserService { + @POST("/user") + fun postUser( + @Body requestUser: RequestUserDto + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt index 9446b27..5585386 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt @@ -9,6 +9,7 @@ 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.material.MaterialTheme.colors import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text @@ -43,8 +44,11 @@ fun SignUpScreen( var userPassWord by remember { mutableStateOf("") } + var userHobby by remember { + mutableStateOf("") + } val context = LocalContext.current - signUpViewModel.initializePreferences(context) +// signUpViewModel.initializePreferences(context) Column( modifier = modifier @@ -88,6 +92,13 @@ fun SignUpScreen( InfoTextWithIcon( text = stringResource(R.string.signup_password_explain) ) + + IDTextField( + value = userHobby, + onValueChange = { userHobby = it }, + modifier = Modifier.fillMaxWidth(), + placeholder = context.getString(R.string.signup_hobby) + ) } @@ -98,18 +109,23 @@ fun SignUpScreen( ) { Button( onClick = { - if (signUpViewModel.isAbleEmail(userId) && signUpViewModel.isAblePassword( - userPassWord - ) - ) { - signUpViewModel.saveUserInfo(userId, userPassWord) - navController.popBackStack() - Toast.makeText(context, (R.string.signup_success), Toast.LENGTH_SHORT) - .show() - } else { - Toast.makeText(context, (R.string.signup_fail), Toast.LENGTH_SHORT) - .show() - } + signUpViewModel.signUpUser( + username = userId, + password = userPassWord, + hobby = userHobby, + onSuccess = { + navController.popBackStack() + Toast.makeText( + context, + R.string.signup_success, + Toast.LENGTH_SHORT + ).show() + }, + onFailure = { errorMessage -> + Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() + } + ) + }, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt index 5447424..ddf6242 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt @@ -1,34 +1,87 @@ package org.sopt.and.ui.signup -import android.content.Context -import android.content.SharedPreferences +import android.util.Log import androidx.lifecycle.ViewModel +import kotlinx.serialization.json.Json +import okhttp3.ResponseBody +import org.sopt.and.api.ServicePool +import org.sopt.and.api.dto.RequestUserDto +import org.sopt.and.api.dto.ResponseErrorDto +import org.sopt.and.api.dto.ResponseUserSuccessDto +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response -class SignUpViewModel: ViewModel() { - val PASSWORD_MIN_LENGTH = 8 - val PASSWORD_MAX_LENGTH = 20 - val PASSWORD_REGEX = Regex("^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@\$!%*?&])[A-Za-z\\d@\$!%*?&]{$PASSWORD_MIN_LENGTH,$PASSWORD_MAX_LENGTH}\$") +class SignUpViewModel : ViewModel() { + private val userService by lazy { ServicePool.userService } - var sharedPreferences: SharedPreferences? = null +// val PASSWORD_MIN_LENGTH = 8 +// val PASSWORD_MAX_LENGTH = 20 +// val PASSWORD_REGEX = +// Regex("^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@\$!%*?&])[A-Za-z\\d@\$!%*?&]{$PASSWORD_MIN_LENGTH,$PASSWORD_MAX_LENGTH}\$") - fun initializePreferences(context: Context){ - sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) - } +// var sharedPreferences: SharedPreferences? = null +// +// fun initializePreferences(context: Context) { +// sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) +// } +// +// fun saveUserInfo(id: String, password: String) { +// sharedPreferences?.edit()?.apply() { +// putString("userId", id) +// putString("userPassWord", password) +// apply() +// } +// } - fun saveUserInfo(id: String, password: String){ - sharedPreferences?.edit()?.apply(){ - putString("userId", id) - putString("userPassWord", password) - apply() - } - } +// fun isAbleEmail(email: String): Boolean { +// return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() +// } +// +// fun isAblePassword(password: String): Boolean { +// return PASSWORD_REGEX.matches(password) +// } - fun isAbleEmail(email: String): Boolean{ - return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() - } + fun signUpUser( + username: String, + password: String, + hobby: String, + onSuccess: () -> Unit, + onFailure: (String) -> Unit + ) { + val requestUserDto = RequestUserDto(username = username, password = password, hobby = hobby) + + userService.postUser(requestUserDto).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val successBody = response.body()?.string() + val successDto = Json.decodeFromString(successBody ?: "") + onSuccess() + } else { + val errorBody = response.errorBody()?.string() + val errorDto = errorBody?.let { Json.decodeFromString(it) } + val errorMessage = when (response.code()) { + 400 -> when (errorDto?.code) { + "01" -> "닉네임, 비밀번호, 취미가 8자를 넘기면 안됩니다." + else -> "잘못된 요청입니다." + } + 409 -> when (errorDto?.code) { + "00" -> "닉네임이 중복됩니다." + else -> "충돌이 발생했습니다." + } + else -> "알 수 없는 오류가 발생했습니다." + } + onFailure(errorMessage) + } + } - fun isAblePassword(password: String): Boolean{ - return PASSWORD_REGEX.matches(password) + override fun onFailure(call: Call, t: Throwable) { + Log.e("SignUpViewModel", "Failure: ${t.message}") + } + }) } } \ 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 28ab24a..f872b4f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ wavve@example.com 로그인, 비밀번호 찾기, 알림에 사용되니 정확한 이메일을 입력해주세요. Wavve 비밀번호 설정 + 취미 설정 비밀번호는 8-20자 이내로 영문 대소문자, 숫자, 특수문자 중 3가지 이상 혼용하여 입력해 주세요. Wavve 회원가입 회원가입 성공 From bb7f46f1c2b812328dd70f338e453afa9fd032b2 Mon Sep 17 00:00:00 2001 From: MinseoSong Date: Wed, 13 Nov 2024 23:53:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?#7=20=EC=84=9C=EB=B2=84=EC=97=B0=EB=8F=99?= =?UTF-8?q?=20-=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/api/ApiFactory.kt | 2 + .../and/api/dto/{ErrorDTO.kt => ErrorDto.kt} | 0 .../java/org/sopt/and/api/dto/LoginDto.kt | 24 +++++ .../and/api/dto/{UserDTO.kt => UserDto.kt} | 0 .../org/sopt/and/api/service/LoginService.kt | 14 +++ .../org/sopt/and/ui/signin/SignInScreen.kt | 16 +++- .../org/sopt/and/ui/signin/SignInViewModel.kt | 92 ++++++++++++++----- .../org/sopt/and/ui/signup/SignUpScreen.kt | 1 - app/src/main/res/values/strings.xml | 4 +- 9 files changed, 126 insertions(+), 27 deletions(-) rename app/src/main/java/org/sopt/and/api/dto/{ErrorDTO.kt => ErrorDto.kt} (100%) create mode 100644 app/src/main/java/org/sopt/and/api/dto/LoginDto.kt rename app/src/main/java/org/sopt/and/api/dto/{UserDTO.kt => UserDto.kt} (100%) create mode 100644 app/src/main/java/org/sopt/and/api/service/LoginService.kt diff --git a/app/src/main/java/org/sopt/and/api/ApiFactory.kt b/app/src/main/java/org/sopt/and/api/ApiFactory.kt index 8fb713a..8da299d 100644 --- a/app/src/main/java/org/sopt/and/api/ApiFactory.kt +++ b/app/src/main/java/org/sopt/and/api/ApiFactory.kt @@ -6,6 +6,7 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.sopt.and.BuildConfig +import org.sopt.and.api.service.LoginService import org.sopt.and.api.service.UserService import retrofit2.Retrofit @@ -33,4 +34,5 @@ object ApiFactory { object ServicePool { val userService = ApiFactory.create() + val loginService = ApiFactory.create() } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/dto/ErrorDTO.kt b/app/src/main/java/org/sopt/and/api/dto/ErrorDto.kt similarity index 100% rename from app/src/main/java/org/sopt/and/api/dto/ErrorDTO.kt rename to app/src/main/java/org/sopt/and/api/dto/ErrorDto.kt diff --git a/app/src/main/java/org/sopt/and/api/dto/LoginDto.kt b/app/src/main/java/org/sopt/and/api/dto/LoginDto.kt new file mode 100644 index 0000000..d3af521 --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/dto/LoginDto.kt @@ -0,0 +1,24 @@ +package org.sopt.and.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseLoginSuccessDto( + @SerialName("result") + val result: LoginData +) + +@Serializable +data class LoginData( + @SerialName("token") + val token: String +) + +@Serializable +data class RequestLoginDto( + @SerialName("username") + val username: String, + @SerialName("password") + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/dto/UserDTO.kt b/app/src/main/java/org/sopt/and/api/dto/UserDto.kt similarity index 100% rename from app/src/main/java/org/sopt/and/api/dto/UserDTO.kt rename to app/src/main/java/org/sopt/and/api/dto/UserDto.kt diff --git a/app/src/main/java/org/sopt/and/api/service/LoginService.kt b/app/src/main/java/org/sopt/and/api/service/LoginService.kt new file mode 100644 index 0000000..ef5390b --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/service/LoginService.kt @@ -0,0 +1,14 @@ +package org.sopt.and.api.service + +import okhttp3.ResponseBody +import org.sopt.and.api.dto.RequestLoginDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface LoginService { + @POST("/login") + fun postLogin( + @Body requestLogin: RequestLoginDto + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt b/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt index ed24498..ce7996b 100644 --- a/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt @@ -54,7 +54,7 @@ fun SignInScreen( val context = LocalContext.current val snackbarHostState = remember { SnackbarHostState() } - signInViewModel.initializePreferences(context) +// signInViewModel.initializePreferences(context) LaunchedEffect(snackbarMessage) { snackbarMessage?.let { message -> @@ -104,8 +104,18 @@ fun SignInScreen( Button( onClick = { - signInViewModel.updateUserInfo(userId, userPassWord) - signInViewModel.performLogin() +// signInViewModel.updateUserInfo(userId, userPassWord) +// signInViewModel.performLogin() + signInViewModel.loginUser( + username = userId, + password = userPassWord, + onSuccess = {tokenMessage -> + signInViewModel._snackbarMessage.value = tokenMessage + }, + onFailure = {erorMessage -> + signInViewModel._snackbarMessage.value = erorMessage + } + ) }, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors( diff --git a/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt b/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt index b2ecaca..97369fd 100644 --- a/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt +++ b/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt @@ -1,44 +1,94 @@ package org.sopt.and.ui.signin -import android.content.Context -import android.content.SharedPreferences +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.json.Json +import okhttp3.ResponseBody +import org.sopt.and.api.ServicePool +import org.sopt.and.api.dto.RequestLoginDto +import org.sopt.and.api.dto.ResponseErrorDto +import org.sopt.and.api.dto.ResponseLoginSuccessDto import org.sopt.and.model.UserInfo +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response class SignInViewModel : ViewModel() { + val loginService by lazy { ServicePool.loginService } var userInfo by mutableStateOf(UserInfo("", "")) - var sharedPreferences: SharedPreferences? = null +// var sharedPreferences: SharedPreferences? = null - private val _snackbarMessage = MutableStateFlow(null) + val _snackbarMessage = MutableStateFlow(null) val snackbarMessage: StateFlow get() = _snackbarMessage - fun initializePreferences(context: Context) { - sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) - } +// fun initializePreferences(context: Context) { +// sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) +// } +// +// fun updateUserInfo(id: String, password: String) { +// userInfo = UserInfo(userId = id, userPassWord = password) +// } - fun updateUserInfo(id: String, password: String) { - userInfo = UserInfo(userId = id, userPassWord = password) - } + fun loginUser( + username: String, + password: String, + onSuccess: (String) -> Unit, + onFailure: (String) -> Unit + ) { + val requestLoginDto = RequestLoginDto(username = username, password = password) + + loginService.postLogin(requestLoginDto).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val successBody = response.body()?.string() + val successDto = + Json.decodeFromString(successBody ?: "") + onSuccess("로그인 성공!") + } else { + val errorBody = response.errorBody()?.string() + val errorDto = errorBody?.let { Json.decodeFromString(it) } + val errorMessage = when (response.code()) { + 400 -> when (errorDto?.code) { + "02" -> "로그인 정보가 올바르지 않습니다." + else -> "잘못된 요청입니다." + } - fun performLogin() { - val savedUserId = sharedPreferences?.getString("userId", "") ?: "" - val savedUserPassword = sharedPreferences?.getString("userPassWord", "") ?: "" - val loginSuccess = - (userInfo.userId == savedUserId && userInfo.userPassWord == savedUserPassword) - - _snackbarMessage.value = if (loginSuccess) { - "로그인 성공" - } else { - "로그인 실패" - } + 403 -> "비밀번호가 틀렸습니다." + else -> "알 수 없는 오류가 발생했습니다." + } + onFailure(errorMessage) + } + } + + override fun onFailure(call: Call, t: Throwable) { + onFailure("네트워크 오류: ${t.message}") + Log.e("SignInViewModel", "Failure: ${t.message}") + } + }) } +// fun performLogin() { +// val savedUserId = sharedPreferences?.getString("userId", "") ?: "" +// val savedUserPassword = sharedPreferences?.getString("userPassWord", "") ?: "" +// val loginSuccess = +// (userInfo.userId == savedUserId && userInfo.userPassWord == savedUserPassword) +// +// _snackbarMessage.value = if (loginSuccess) { +// "로그인 성공" +// } else { +// "로그인 실패" +// } +// } + fun clearSnackbarMessage() { _snackbarMessage.value = null } diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt index 5585386..50f5f59 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt @@ -9,7 +9,6 @@ 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.material.MaterialTheme.colors import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f872b4f..b599df9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,9 +19,10 @@ 비밀번호 재설정 회원가입 | - 로그인 성공 + 로그인 성공! 로그인 실패 + // My 화면 첫 결제 시 첫 달 100원! 현재 보유하신 이용권이 없습니다. @@ -43,7 +44,6 @@ 키즈 영화플러스 %1$d / %2$d - 믿고 보는 웨이브 에디터 추천작 오늘의 TOP 20 From 6b6724f2714359abd74415c001f6a45f5f5e8513 Mon Sep 17 00:00:00 2001 From: MinseoSong Date: Thu, 14 Nov 2024 00:17:25 +0900 Subject: [PATCH 5/7] =?UTF-8?q?#7=20=EC=84=9C=EB=B2=84=EC=97=B0=EB=8F=99?= =?UTF-8?q?=20-=20=ED=86=A0=ED=81=B0=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/sopt/and/navigate/NavGraph.kt | 7 +--- .../main/java/org/sopt/and/ui/my/MyScreen.kt | 11 ++++-- .../org/sopt/and/ui/signin/SignInScreen.kt | 3 +- .../org/sopt/and/ui/signin/SignInViewModel.kt | 38 ++++++------------- 4 files changed, 22 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/sopt/and/navigate/NavGraph.kt b/app/src/main/java/org/sopt/and/navigate/NavGraph.kt index 61e6ace..6f27d30 100644 --- a/app/src/main/java/org/sopt/and/navigate/NavGraph.kt +++ b/app/src/main/java/org/sopt/and/navigate/NavGraph.kt @@ -65,12 +65,7 @@ fun NavGraph(navController: NavHostController) { signUpViewModel = signUpViewModel ) } - composable(Routes.My.route) { - MyScreen( - navController, - signInViewModel = signInViewModel - ) - } + composable(Routes.My.route) { MyScreen(navController) } composable(Routes.Search.route) { SearchScreen(navController) } composable(Routes.Home.route) { HomeScreen(navController) } } diff --git a/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt b/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt index 1717cd0..66eb13c 100644 --- a/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt @@ -1,5 +1,6 @@ package org.sopt.and.ui.my +import android.content.Context import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -30,10 +32,11 @@ import org.sopt.and.ui.signin.SignInViewModel @Composable fun MyScreen( - navController: NavHostController, - signInViewModel: SignInViewModel + navController: NavHostController ) { - val userId = signInViewModel.userInfo.userId + val context = LocalContext.current + val sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) + val token = sharedPreferences.getString("token", "") Column( modifier = Modifier.fillMaxSize() @@ -59,7 +62,7 @@ fun MyScreen( contentScale = ContentScale.Fit ) Text( - text = stringResource(R.string.my_nickname, userId), + text = stringResource(R.string.my_nickname, token!!), color = Color.White, modifier = Modifier.weight(1f) ) diff --git a/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt b/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt index ce7996b..098c8bd 100644 --- a/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt @@ -54,7 +54,8 @@ fun SignInScreen( val context = LocalContext.current val snackbarHostState = remember { SnackbarHostState() } -// signInViewModel.initializePreferences(context) + + signInViewModel.initializePreferences(context) LaunchedEffect(snackbarMessage) { snackbarMessage?.let { message -> diff --git a/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt b/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt index 97369fd..b9cd278 100644 --- a/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt +++ b/app/src/main/java/org/sopt/and/ui/signin/SignInViewModel.kt @@ -1,9 +1,8 @@ package org.sopt.and.ui.signin +import android.content.Context +import android.content.SharedPreferences import android.util.Log -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -13,7 +12,6 @@ import org.sopt.and.api.ServicePool import org.sopt.and.api.dto.RequestLoginDto import org.sopt.and.api.dto.ResponseErrorDto import org.sopt.and.api.dto.ResponseLoginSuccessDto -import org.sopt.and.model.UserInfo import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -21,19 +19,18 @@ import retrofit2.Response class SignInViewModel : ViewModel() { val loginService by lazy { ServicePool.loginService } - var userInfo by mutableStateOf(UserInfo("", "")) -// var sharedPreferences: SharedPreferences? = null + private var sharedPreferences: SharedPreferences? = null val _snackbarMessage = MutableStateFlow(null) val snackbarMessage: StateFlow get() = _snackbarMessage -// fun initializePreferences(context: Context) { -// sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) -// } -// -// fun updateUserInfo(id: String, password: String) { -// userInfo = UserInfo(userId = id, userPassWord = password) -// } + fun initializePreferences(context: Context) { + sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) + } + + private fun saveToken(token: String) { + sharedPreferences?.edit()?.putString("token", token)?.apply() + } fun loginUser( username: String, @@ -52,6 +49,8 @@ class SignInViewModel : ViewModel() { val successBody = response.body()?.string() val successDto = Json.decodeFromString(successBody ?: "") + val token = successDto.result.token + saveToken(token) onSuccess("로그인 성공!") } else { val errorBody = response.errorBody()?.string() @@ -76,19 +75,6 @@ class SignInViewModel : ViewModel() { }) } -// fun performLogin() { -// val savedUserId = sharedPreferences?.getString("userId", "") ?: "" -// val savedUserPassword = sharedPreferences?.getString("userPassWord", "") ?: "" -// val loginSuccess = -// (userInfo.userId == savedUserId && userInfo.userPassWord == savedUserPassword) -// -// _snackbarMessage.value = if (loginSuccess) { -// "로그인 성공" -// } else { -// "로그인 실패" -// } -// } - fun clearSnackbarMessage() { _snackbarMessage.value = null } From 9d9b3da34b194debf41f8ec22acd7afb633c7b79 Mon Sep 17 00:00:00 2001 From: MinseoSong Date: Thu, 14 Nov 2024 02:21:50 +0900 Subject: [PATCH 6/7] =?UTF-8?q?#7=20=EC=84=9C=EB=B2=84=EC=97=B0=EB=8F=99?= =?UTF-8?q?=20-=20=EB=82=B4=20=EC=B7=A8=EB=AF=B8=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/and/api/ApiFactory.kt | 2 + .../java/org/sopt/and/api/dto/HobbyDto.kt | 16 +++++ .../org/sopt/and/api/service/HobbyService.kt | 13 +++++ .../java/org/sopt/and/navigate/NavGraph.kt | 9 ++- .../main/java/org/sopt/and/ui/my/MyScreen.kt | 21 +++++-- .../java/org/sopt/and/ui/my/MyViewModel.kt | 58 +++++++++++++++++++ 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/sopt/and/api/dto/HobbyDto.kt create mode 100644 app/src/main/java/org/sopt/and/api/service/HobbyService.kt create mode 100644 app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt diff --git a/app/src/main/java/org/sopt/and/api/ApiFactory.kt b/app/src/main/java/org/sopt/and/api/ApiFactory.kt index 8da299d..99d6251 100644 --- a/app/src/main/java/org/sopt/and/api/ApiFactory.kt +++ b/app/src/main/java/org/sopt/and/api/ApiFactory.kt @@ -6,6 +6,7 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.sopt.and.BuildConfig +import org.sopt.and.api.service.HobbyService import org.sopt.and.api.service.LoginService import org.sopt.and.api.service.UserService import retrofit2.Retrofit @@ -35,4 +36,5 @@ object ApiFactory { object ServicePool { val userService = ApiFactory.create() val loginService = ApiFactory.create() + val hobbyService = ApiFactory.create() } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/dto/HobbyDto.kt b/app/src/main/java/org/sopt/and/api/dto/HobbyDto.kt new file mode 100644 index 0000000..ba29b3d --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/dto/HobbyDto.kt @@ -0,0 +1,16 @@ +package org.sopt.and.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseHobbySuccessDto( + @SerialName("result") + val result: HobbyData +) + +@Serializable +data class HobbyData( + @SerialName("hobby") + val hobby: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/api/service/HobbyService.kt b/app/src/main/java/org/sopt/and/api/service/HobbyService.kt new file mode 100644 index 0000000..530a0f4 --- /dev/null +++ b/app/src/main/java/org/sopt/and/api/service/HobbyService.kt @@ -0,0 +1,13 @@ +package org.sopt.and.api.service + +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header + +interface HobbyService { + @GET("/user/my-hobby") + fun getMyHobby( + @Header("token") token: String + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/navigate/NavGraph.kt b/app/src/main/java/org/sopt/and/navigate/NavGraph.kt index 6f27d30..0c4c5eb 100644 --- a/app/src/main/java/org/sopt/and/navigate/NavGraph.kt +++ b/app/src/main/java/org/sopt/and/navigate/NavGraph.kt @@ -17,6 +17,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import org.sopt.and.component.bar.BottomBar import org.sopt.and.ui.home.HomeScreen import org.sopt.and.ui.my.MyScreen +import org.sopt.and.ui.my.MyViewModel import org.sopt.and.ui.search.SearchScreen import org.sopt.and.ui.signin.SignInScreen import org.sopt.and.ui.signin.SignInViewModel @@ -27,6 +28,7 @@ import org.sopt.and.ui.signup.SignUpViewModel fun NavGraph(navController: NavHostController) { val signUpViewModel: SignUpViewModel = viewModel() val signInViewModel: SignInViewModel = viewModel() + val myViewModel: MyViewModel = viewModel() val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route @@ -65,7 +67,12 @@ fun NavGraph(navController: NavHostController) { signUpViewModel = signUpViewModel ) } - composable(Routes.My.route) { MyScreen(navController) } + composable(Routes.My.route) { + MyScreen( + navController, + myViewModel = myViewModel + ) + } composable(Routes.Search.route) { SearchScreen(navController) } composable(Routes.Home.route) { HomeScreen(navController) } } diff --git a/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt b/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt index 66eb13c..001adc2 100644 --- a/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/my/MyScreen.kt @@ -15,6 +15,10 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -28,15 +32,22 @@ import androidx.navigation.NavHostController import org.sopt.and.R import org.sopt.and.component.BuyTextButton import org.sopt.and.component.MyPageItem -import org.sopt.and.ui.signin.SignInViewModel @Composable fun MyScreen( - navController: NavHostController + navController: NavHostController, + myViewModel: MyViewModel ) { val context = LocalContext.current - val sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) - val token = sharedPreferences.getString("token", "") + val sharedPreferences = remember { + context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) + } + + LaunchedEffect(Unit) { + myViewModel.getUserHobby(sharedPreferences) + } + + val hobby by myViewModel.hobby.collectAsState() Column( modifier = Modifier.fillMaxSize() @@ -62,7 +73,7 @@ fun MyScreen( contentScale = ContentScale.Fit ) Text( - text = stringResource(R.string.my_nickname, token!!), + text = stringResource(R.string.my_nickname, hobby), color = Color.White, modifier = Modifier.weight(1f) ) diff --git a/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt b/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt new file mode 100644 index 0000000..5fdd17d --- /dev/null +++ b/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt @@ -0,0 +1,58 @@ +package org.sopt.and.ui.my + +import android.content.SharedPreferences +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json +import okhttp3.ResponseBody +import org.sopt.and.api.ServicePool +import org.sopt.and.api.dto.ResponseErrorDto +import org.sopt.and.api.dto.ResponseHobbySuccessDto +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class MyViewModel(): ViewModel() { + private val hobbyService = ServicePool.hobbyService + + private val _hobby = MutableStateFlow("") + val hobby: StateFlow get() = _hobby + + fun getUserHobby(sharedPreferences: SharedPreferences) { + val token = sharedPreferences.getString("token", null) + if (token.isNullOrEmpty()) { + Log.e("MyViewModel", "토큰 없음") + return + } + + hobbyService.getMyHobby(token).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val successBody = response.body()?.string() + val successDto = Json.decodeFromString(successBody ?: "") + viewModelScope.launch { + _hobby.emit(successDto.result.hobby) + } + } else { + val errorBody = response.errorBody()?.string() + val errorDto = errorBody?.let { Json.decodeFromString(it) } + val errorMessage = when (response.code()) { + 401 -> "토큰이 없습니다." + 403 -> "유효하지 않은 토큰입니다." + 404 -> "잘못된 경로로 요청했습니다." + else -> "알 수 없는 오류가 발생했습니다." + } + Log.e("MyViewModel", errorMessage) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("MyViewModel", "Network error: ${t.message}") + } + }) + } +} \ No newline at end of file From 949c18d8edc4a4d471f7cf120e74300b83ca2ebe Mon Sep 17 00:00:00 2001 From: MinseoSong Date: Thu, 14 Nov 2024 02:41:00 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=EC=A4=84=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/sopt/and/ui/my/MyViewModel.kt | 5 +-- .../org/sopt/and/ui/signin/SignInScreen.kt | 4 +-- .../org/sopt/and/ui/signup/SignUpScreen.kt | 1 - .../org/sopt/and/ui/signup/SignUpViewModel.kt | 32 +++---------------- 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt b/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt index 5fdd17d..1c21a34 100644 --- a/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt +++ b/app/src/main/java/org/sopt/and/ui/my/MyViewModel.kt @@ -16,7 +16,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response -class MyViewModel(): ViewModel() { +class MyViewModel() : ViewModel() { private val hobbyService = ServicePool.hobbyService private val _hobby = MutableStateFlow("") @@ -33,7 +33,8 @@ class MyViewModel(): ViewModel() { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { val successBody = response.body()?.string() - val successDto = Json.decodeFromString(successBody ?: "") + val successDto = + Json.decodeFromString(successBody ?: "") viewModelScope.launch { _hobby.emit(successDto.result.hobby) } diff --git a/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt b/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt index 098c8bd..baca449 100644 --- a/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signin/SignInScreen.kt @@ -110,10 +110,10 @@ fun SignInScreen( signInViewModel.loginUser( username = userId, password = userPassWord, - onSuccess = {tokenMessage -> + onSuccess = { tokenMessage -> signInViewModel._snackbarMessage.value = tokenMessage }, - onFailure = {erorMessage -> + onFailure = { erorMessage -> signInViewModel._snackbarMessage.value = erorMessage } ) diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt index 50f5f59..69876a1 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpScreen.kt @@ -47,7 +47,6 @@ fun SignUpScreen( mutableStateOf("") } val context = LocalContext.current -// signUpViewModel.initializePreferences(context) Column( modifier = modifier diff --git a/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt index ddf6242..0b29146 100644 --- a/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/ui/signup/SignUpViewModel.kt @@ -16,33 +16,6 @@ import retrofit2.Response class SignUpViewModel : ViewModel() { private val userService by lazy { ServicePool.userService } -// val PASSWORD_MIN_LENGTH = 8 -// val PASSWORD_MAX_LENGTH = 20 -// val PASSWORD_REGEX = -// Regex("^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@\$!%*?&])[A-Za-z\\d@\$!%*?&]{$PASSWORD_MIN_LENGTH,$PASSWORD_MAX_LENGTH}\$") - -// var sharedPreferences: SharedPreferences? = null -// -// fun initializePreferences(context: Context) { -// sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) -// } -// -// fun saveUserInfo(id: String, password: String) { -// sharedPreferences?.edit()?.apply() { -// putString("userId", id) -// putString("userPassWord", password) -// apply() -// } -// } - -// fun isAbleEmail(email: String): Boolean { -// return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() -// } -// -// fun isAblePassword(password: String): Boolean { -// return PASSWORD_REGEX.matches(password) -// } - fun signUpUser( username: String, password: String, @@ -59,7 +32,8 @@ class SignUpViewModel : ViewModel() { ) { if (response.isSuccessful) { val successBody = response.body()?.string() - val successDto = Json.decodeFromString(successBody ?: "") + val successDto = + Json.decodeFromString(successBody ?: "") onSuccess() } else { val errorBody = response.errorBody()?.string() @@ -69,10 +43,12 @@ class SignUpViewModel : ViewModel() { "01" -> "닉네임, 비밀번호, 취미가 8자를 넘기면 안됩니다." else -> "잘못된 요청입니다." } + 409 -> when (errorDto?.code) { "00" -> "닉네임이 중복됩니다." else -> "충돌이 발생했습니다." } + else -> "알 수 없는 오류가 발생했습니다." } onFailure(errorMessage)