From bf28c1f034d8cdee8f7cb29b9b94383a40f964ac Mon Sep 17 00:00:00 2001 From: arinming Date: Fri, 3 May 2024 15:17:51 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[Feat]=20data=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EA=B5=AC=EC=B6=95=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 37 ++++++++++++++--- .../now/compose/data/{ => model}/Profile.kt | 2 +- .../compose/data/model/RequestSignInDto.kt | 12 ++++++ .../compose/data/model/RequestSignUpDto.kt | 16 ++++++++ .../now/compose/data/model/ResponseInfoDto.kt | 24 +++++++++++ .../compose/data/model/ResponseSignInDto.kt | 12 ++++++ .../compose/data/model/ResponseSignUpDto.kt | 12 ++++++ .../now/compose/data/model/ResponseUserDto.kt | 29 ++++++++++++++ .../now/compose/data/module/ApiFactory.kt | 40 +++++++++++++++++++ .../compose/data/module/AuthInterceptor.kt | 17 ++++++++ .../now/compose/data/network/AuthService.kt | 27 +++++++++++++ .../compose/data/network/FollowerService.kt | 13 ++++++ .../sopt/now/compose/ui/home/HomeScreen.kt | 2 +- 13 files changed, 236 insertions(+), 7 deletions(-) rename app/src/main/java/com/sopt/now/compose/data/{ => model}/Profile.kt (71%) create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/RequestSignInDto.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/RequestSignUpDto.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/ResponseSignInDto.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/ResponseSignUpDto.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/network/AuthService.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/network/FollowerService.kt diff --git a/app/build.gradle b/app/build.gradle index 28b5267..067645f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,9 @@ plugins { id 'org.jetbrains.kotlin.android' } +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + android { namespace 'com.sopt.now.compose' compileSdk 34 @@ -18,6 +21,8 @@ android { vectorDrawables { useSupportLibrary true } + buildConfigField "String", "AUTH_BASE_URL", properties["base.url"] + buildConfigField "String", "FOLLOWER_URL", properties["follower.url"] } buildTypes { @@ -35,6 +40,7 @@ android { } buildFeatures { compose true + buildConfig true } composeOptions { kotlinCompilerExtensionVersion '1.5.1' @@ -47,10 +53,10 @@ 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.04.00') + implementation 'androidx.activity:activity-compose:1.9.0' + implementation platform('androidx.compose:compose-bom:2024.05.00') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' @@ -59,9 +65,30 @@ dependencies { 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.04.00') + androidTestImplementation platform('androidx.compose:compose-bom:2024.05.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' - implementation 'androidx.compose.material:material:1.6.6' + implementation 'androidx.compose.material:material:1.6.7' + + // Lifecycle Viewmodel + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0' + + // Fragment && Activity + implementation 'androidx.fragment:fragment-ktx:1.6.2' + implementation 'androidx.activity:activity-ktx:1.9.0' + + // Retrofit + 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' + + // OKHttp + implementation platform('com.squareup.okhttp3:okhttp-bom:4.10.0') + implementation 'com.squareup.okhttp3:okhttp' + implementation 'com.squareup.okhttp3:logging-interceptor' + + // Glide + implementation 'com.github.bumptech.glide:glide:4.13.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/Profile.kt b/app/src/main/java/com/sopt/now/compose/data/model/Profile.kt similarity index 71% rename from app/src/main/java/com/sopt/now/compose/data/Profile.kt rename to app/src/main/java/com/sopt/now/compose/data/model/Profile.kt index 55e79b2..9025131 100644 --- a/app/src/main/java/com/sopt/now/compose/data/Profile.kt +++ b/app/src/main/java/com/sopt/now/compose/data/model/Profile.kt @@ -1,4 +1,4 @@ -package com.sopt.now.compose.data +package com.sopt.now.compose.data.model data class Profile( val profileImage: Int, diff --git a/app/src/main/java/com/sopt/now/compose/data/model/RequestSignInDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/RequestSignInDto.kt new file mode 100644 index 0000000..f9d65de --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/model/RequestSignInDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestSignInDto( + @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/model/RequestSignUpDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/RequestSignUpDto.kt new file mode 100644 index 0000000..e004cf4 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/model/RequestSignUpDto.kt @@ -0,0 +1,16 @@ +package com.sopt.now.compose.data.model + +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/model/ResponseInfoDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt new file mode 100644 index 0000000..20cd109 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt @@ -0,0 +1,24 @@ +package com.sopt.now.compose.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseInfoDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, + @SerialName("data") + val data: UserInfo, +) + +@Serializable +data class UserInfo( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("nickname") + val nickname: String, + @SerialName("phone") + val phone: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/model/ResponseSignInDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/ResponseSignInDto.kt new file mode 100644 index 0000000..4562abd --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/model/ResponseSignInDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseSignInDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/model/ResponseSignUpDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/ResponseSignUpDto.kt new file mode 100644 index 0000000..4046ee1 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/model/ResponseSignUpDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseSignUpDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt new file mode 100644 index 0000000..1093553 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt @@ -0,0 +1,29 @@ +package com.sopt.now.compose.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseUserDto( + val page: Int, + @SerialName("per_page") val perPage: Int, + val total: Int, + @SerialName("total_pages") val totalPages: Int, + val data: List, + val support: Support, +) + +@Serializable +data class UserData( + val id: Int, + val email: String, + @SerialName("first_name") val firstName: String, + @SerialName("last_name") val lastName: String, + val avatar: String, +) + +@Serializable +data class Support( + val url: String, + val text: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt b/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt new file mode 100644 index 0000000..cbf76d3 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt @@ -0,0 +1,40 @@ +package com.sopt.now.compose.data.module + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.sopt.now.compose.BuildConfig +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import retrofit2.Retrofit + + +object ApiFactory { + private const val BASE_URL: String = BuildConfig.AUTH_BASE_URL + private const val FOLLOWER_URL: String = BuildConfig.FOLLOWER_URL + + private val interceptorClient = OkHttpClient().newBuilder() + .addInterceptor(AuthInterceptor()).build() + + val baseRetrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(interceptorClient) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + val followerRetrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(FOLLOWER_URL) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + inline fun createBase(): T = baseRetrofit.create(T::class.java) + inline fun createFollower(): T = followerRetrofit.create(T::class.java) +} + +object ServicePool { + val authService = ApiFactory.createBase() + val followerService = ApiFactory.createFollower() +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt b/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt new file mode 100644 index 0000000..83af933 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt @@ -0,0 +1,17 @@ +package com.sopt.now.compose.data.module + +import okhttp3.Interceptor +import okhttp3.Response + +class AuthInterceptor : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + val newRequest = originalRequest.newBuilder() + .addHeader("memberId", "495") + .build() + + return chain.proceed(newRequest) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/network/AuthService.kt b/app/src/main/java/com/sopt/now/compose/data/network/AuthService.kt new file mode 100644 index 0000000..5652628 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/network/AuthService.kt @@ -0,0 +1,27 @@ +package com.sopt.now.compose.data.network + +import com.sopt.now.compose.data.model.RequestSignInDto +import com.sopt.now.compose.data.model.RequestSignUpDto +import com.sopt.now.compose.data.model.ResponseInfoDto +import com.sopt.now.compose.data.model.ResponseSignInDto +import com.sopt.now.compose.data.model.ResponseSignUpDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface AuthService { + @POST("member/join") + fun signUp( + @Body request: RequestSignUpDto, + ): Call + + @POST("member/login") + fun signIn( + @Body request: RequestSignInDto, + ): Call + + @GET("member/info") + fun memberInfo( + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/network/FollowerService.kt b/app/src/main/java/com/sopt/now/compose/data/network/FollowerService.kt new file mode 100644 index 0000000..e9fbe43 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/network/FollowerService.kt @@ -0,0 +1,13 @@ +package com.sopt.now.compose.data.network + +import com.sopt.now.compose.data.model.ResponseUserDto +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query + +interface FollowerService { + @GET("/api/users") + fun getUserList( + @Query("page") page: Int, + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt index ec67019..01371c3 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.sopt.now.compose.R -import com.sopt.now.compose.data.Profile +import com.sopt.now.compose.data.model.Profile @Composable fun HomeScreen() { From 3094e499b66fd4b000a2ab39af83b643cc5b519c Mon Sep 17 00:00:00 2001 From: arinming Date: Fri, 3 May 2024 16:06:24 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[Feat]=20SignUp=20API=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 3 +- app/src/main/AndroidManifest.xml | 3 + .../now/compose/data/model/SignInState.kt | 6 ++ .../now/compose/data/model/SignUpState.kt | 6 ++ .../now/compose/data/module/ApiFactory.kt | 2 + .../now/compose/ui/base/SoptAppNavHost.kt | 30 +------- .../sopt/now/compose/ui/login/LoginScreen.kt | 10 +-- .../now/compose/ui/signUp/SignUpScreen.kt | 73 +++++++++++-------- .../now/compose/ui/signUp/SignUpViewModel.kt | 58 +++++++++++++++ app/src/main/res/values/strings.xml | 4 +- build.gradle | 1 + 11 files changed, 125 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/SignInState.kt create mode 100644 app/src/main/java/com/sopt/now/compose/data/model/SignUpState.kt create mode 100644 app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index 067645f..63fdb13 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' } Properties properties = new Properties() @@ -43,7 +44,7 @@ android { buildConfig true } composeOptions { - kotlinCompilerExtensionVersion '1.5.1' + kotlinCompilerExtensionVersion '1.5.5' } packagingOptions { resources { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9fa545..f73f094 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,11 @@ + + - val id = backStackEntry.arguments?.getString("id") - val pw = backStackEntry.arguments?.getString("pw") - val nickname = backStackEntry.arguments?.getString("nickname") - val mbti = backStackEntry.arguments?.getString("mbti") + NavHost(navController = navController, startDestination = "sign_in") { + composable("sign_in") { LoginScreen( onNavigateToHome = navController, onNavigateToSignUp = { navController.navigate("signup") }, - id.toString(), pw.toString(), nickname.toString(), mbti.toString() ) } @@ -75,7 +51,7 @@ fun SoptAppNavHost() { MainScreen(id.toString(), pw.toString(), nickname.toString(), mbti.toString()) } composable("signup") { - SignUpScreen(onNavigateToLogin = navController) + SignUpScreen(onNavigateToSignIn = navController) } } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt index 14dd04c..b66e822 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt @@ -1,6 +1,5 @@ package com.sopt.now.compose.ui.login -import android.util.Log import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -27,10 +26,6 @@ import com.sopt.now.compose.ui.base.SoptPasswordTextField fun LoginScreen( onNavigateToHome: NavHostController, onNavigateToSignUp: () -> Unit, - id: String, - pw: String, - nickname: String, - mbti: String, ) { Column( modifier = Modifier.fillMaxSize(), @@ -70,10 +65,7 @@ fun LoginScreen( SoptOutlinedButton( text = R.string.btn_login, onClick = { - Log.d("Login", "$textId, $id") - if (textId == id && textPw == pw && isLoginButtonEnabled) { - onNavigateToHome.navigate("main?id=$textId&pw=$textPw&nickname=$nickname&mbti=$mbti") - } + }, enabled = true ) diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt index 2f993cc..e029021 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.rememberScrollState 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.mutableStateOf import androidx.compose.runtime.remember @@ -26,8 +27,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import com.sopt.now.compose.R +import com.sopt.now.compose.data.model.RequestSignUpDto import com.sopt.now.compose.ui.base.SoptInputTextField import com.sopt.now.compose.ui.base.SoptOutlinedButton import com.sopt.now.compose.ui.base.SoptPasswordTextField @@ -35,9 +38,25 @@ import kotlinx.coroutines.launch @Composable fun SignUpScreen( - onNavigateToLogin: NavHostController, + onNavigateToSignIn: NavHostController, + signUpViewModel: SignUpViewModel = viewModel(), ) { val context = LocalContext.current + val signUpState by signUpViewModel.signUpState.collectAsState() + + var id by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var nickname by remember { mutableStateOf("") } + var phone by remember { mutableStateOf("") } + + LaunchedEffect(signUpState) { + if (signUpState.isSuccess) { + Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() + onNavigateToSignIn.navigate("sign_in") + } else if (signUpState.message.isNotBlank()) { + Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() + } + } Column( modifier = Modifier.fillMaxSize(), @@ -54,24 +73,11 @@ fun SignUpScreen( } } - var textId by remember { - mutableStateOf("") - } - var textPw by remember { - mutableStateOf("") - } - var textNickname by remember { - mutableStateOf("") - } - var textMbti by remember { - mutableStateOf("") - } - - val isSignUpButtonEnabled by remember(textId, textPw, textNickname, textMbti) { + val isSignUpButtonEnabled by remember(id, password, nickname, phone) { mutableStateOf( - textId.length in 6..10 && textPw.length in 8..12 && textNickname.isNotEmpty() && !textNickname.contains( + id.length in 6..10 && password.length in 8..12 && nickname.isNotEmpty() && !nickname.contains( " " - ) && textMbti.length == 4 + ) && phone.matches(Regex("^010-\\d{4}-\\d{4}\$")) ) } @@ -85,30 +91,33 @@ fun SignUpScreen( item { SoptInputTextField( text = R.string.hint_id, - value = textId, - onValueChange = { textId = it }) + value = id, + onValueChange = { id = it }) SoptPasswordTextField( text = R.string.hint_pw, - value = textPw, - onValueChange = { textPw = it }) + value = password, + onValueChange = { password = it }) SoptInputTextField( text = R.string.hint_nickname, - value = textNickname, - onValueChange = { textNickname = it }) + value = nickname, + onValueChange = { nickname = it }) SoptInputTextField( - text = R.string.hint_mbti, - value = textMbti, - onValueChange = { textMbti = it }) + text = R.string.hint_phone, + value = phone, + onValueChange = { phone = it }) } } SoptOutlinedButton(text = R.string.btn_sign_up, onClick = { if (isSignUpButtonEnabled) { - Toast.makeText( - context, - "회원가입 성공", - Toast.LENGTH_SHORT - ).show() - onNavigateToLogin.navigate("login?id=$textId&pw=$textPw&nickname=$textNickname&mbti=$textMbti") + signUpViewModel.signUp( + RequestSignUpDto( + authenticationId = id, + password = password, + nickname = nickname, + phone = phone + ) + ) + onNavigateToSignIn.navigate("sign_in") } else { Toast.makeText( context, diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt new file mode 100644 index 0000000..d0f71c8 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt @@ -0,0 +1,58 @@ +package com.sopt.now.compose.ui.signUp + +import android.util.Log +import androidx.lifecycle.ViewModel +import com.sopt.now.compose.data.model.RequestSignUpDto +import com.sopt.now.compose.data.model.ResponseSignUpDto +import com.sopt.now.compose.data.model.SignUpState +import com.sopt.now.compose.data.module.ServicePool +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + + +class SignUpViewModel : ViewModel() { + private val authService by lazy { ServicePool.authService } + + private val _signUpState = MutableStateFlow(SignUpState(isSuccess = false, message = "")) + val signUpState = _signUpState.asStateFlow() + + fun signUp(request: RequestSignUpDto) { + authService.signUp(request).enqueue( + object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val userId = response.headers()["location"] + _signUpState.update { + SignUpState( + isSuccess = true, + message = "가입된 유저 아이디는 $userId" + ) + } + } else { + val error = response.code() + _signUpState.update { + SignUpState( + isSuccess = false, + message = "회원가입 실패 : $error" + ) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + _signUpState.update { + SignUpState(isSuccess = false, message = "서버 에러") + } + Log.d("SignUp", "${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 8f65a49..bef4fda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,7 +6,7 @@ ID Password Nickname - MBTI + 전화번호 로그인 회원가입 @@ -15,7 +15,7 @@ 아이디를 입력해주세요. 비밀번호를 입력해주세요. 닉네임을 입력해주세요. - MBTI를 입력해주세요. + 전화번호를 입력해주세요. diff --git a/build.gradle b/build.gradle index be6601c..4c76592 100644 --- a/build.gradle +++ b/build.gradle @@ -3,4 +3,5 @@ plugins { id 'com.android.application' version '8.3.1' apply false id 'com.android.library' version '8.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.9.0' apply false + id("org.jetbrains.kotlin.plugin.serialization") version "1.9.20" apply false } \ No newline at end of file From 3c9c7c70b0ae06ac93118d3b66b833299e894340 Mon Sep 17 00:00:00 2001 From: arinming Date: Fri, 3 May 2024 16:18:48 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[Feat]=20SignIn=20API=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sopt/now/compose/ui/MainScreen.kt | 6 +- .../now/compose/ui/base/SoptAppNavHost.kt | 34 +----- .../sopt/now/compose/ui/login/LoginScreen.kt | 79 ------------- .../now/compose/ui/myPage/MyPageScreen.kt | 16 +-- .../now/compose/ui/signIn/SignInScreen.kt | 106 ++++++++++++++++++ .../now/compose/ui/signIn/SignInViewModel.kt | 55 +++++++++ .../now/compose/ui/signUp/SignUpScreen.kt | 1 - app/src/main/res/values/strings.xml | 6 +- 8 files changed, 175 insertions(+), 128 deletions(-) delete mode 100644 app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt create mode 100644 app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt create mode 100644 app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt diff --git a/app/src/main/java/com/sopt/now/compose/ui/MainScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/MainScreen.kt index 77297f9..b460dae 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/MainScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/MainScreen.kt @@ -15,10 +15,6 @@ import com.sopt.now.compose.ui.search.SearchScreen @Composable fun MainScreen( - id: String, - pw: String, - nickname: String, - mbti: String, ) { val navController = rememberNavController() Scaffold( @@ -36,7 +32,7 @@ fun MainScreen( SearchScreen() } composable(Screen.MyPage.route) { - MyPageScreen(id, pw, nickname, mbti) + MyPageScreen() } } } diff --git a/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt b/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt index 88e6a7a..58ebb65 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt @@ -2,13 +2,11 @@ package com.sopt.now.compose.ui.base import androidx.compose.runtime.Composable import androidx.navigation.NavHostController -import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument import com.sopt.now.compose.ui.MainScreen -import com.sopt.now.compose.ui.login.LoginScreen +import com.sopt.now.compose.ui.signIn.SignInScreen import com.sopt.now.compose.ui.signUp.SignUpScreen @Composable @@ -17,38 +15,16 @@ fun SoptAppNavHost() { NavHost(navController = navController, startDestination = "sign_in") { composable("sign_in") { - LoginScreen( + SignInScreen( onNavigateToHome = navController, onNavigateToSignUp = { navController.navigate("signup") }, ) } composable( - "main?id={id}&pw={pw}&nickname={nickname}&mbti={mbti}", - arguments = listOf( - navArgument("id") { - type = NavType.StringType - defaultValue = "" - }, - navArgument("pw") { - type = NavType.StringType - defaultValue = "" - }, - navArgument("nickname") { - type = NavType.StringType - defaultValue = "" - }, - navArgument("mbti") { - type = NavType.StringType - defaultValue = "" - }, - ), - ) { backStackEntry -> - val id = backStackEntry.arguments?.getString("id") - val pw = backStackEntry.arguments?.getString("pw") - val nickname = backStackEntry.arguments?.getString("nickname") - val mbti = backStackEntry.arguments?.getString("mbti") - MainScreen(id.toString(), pw.toString(), nickname.toString(), mbti.toString()) + "main", + ) { + MainScreen() } composable("signup") { SignUpScreen(onNavigateToSignIn = navController) diff --git a/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt deleted file mode 100644 index b66e822..0000000 --- a/app/src/main/java/com/sopt/now/compose/ui/login/LoginScreen.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.sopt.now.compose.ui.login - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -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.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavHostController -import com.sopt.now.compose.R -import com.sopt.now.compose.ui.base.SoptInputTextField -import com.sopt.now.compose.ui.base.SoptOutlinedButton -import com.sopt.now.compose.ui.base.SoptPasswordTextField - -@Composable -fun LoginScreen( - onNavigateToHome: NavHostController, - onNavigateToSignUp: () -> Unit, -) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween - ) { - var textId by remember { - mutableStateOf("") - } - var textPw by remember { - mutableStateOf("") - } - - - val isLoginButtonEnabled by remember(textId, textPw) { - mutableStateOf( - textId.isNotEmpty() && textPw.isNotEmpty() - ) - } - - Text( - text = stringResource(id = R.string.login_screen_title), - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(16.dp) - ) - Column { - SoptInputTextField( - text = R.string.label_id, value = textId, onValueChange = { textId = it } - ) - SoptPasswordTextField( - text = R.string.label_pw, value = textPw, onValueChange = { textPw = it } - ) - } - - Column { - SoptOutlinedButton( - text = R.string.btn_login, - onClick = { - - }, - enabled = true - ) - SoptOutlinedButton( - text = R.string.btn_sign_up, - onClick = onNavigateToSignUp, - enabled = true - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt index 8110da6..ba7dff2 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf @@ -19,12 +18,7 @@ import androidx.compose.ui.res.painterResource import com.sopt.now.compose.R @Composable -fun MyPageScreen( - id: String, - pw: String, - nickname: String, - mbti: String, -) { +fun MyPageScreen() { var offset by remember { mutableFloatStateOf(0f) } Column(modifier = Modifier @@ -43,10 +37,10 @@ fun MyPageScreen( modifier = Modifier.aspectRatio(3f / 4f) ) Column { - Text(text = id) - Text(text = pw) - Text(text = nickname) - Text(text = mbti) +// Text(text = id) +// Text(text = pw) +// Text(text = nickname) +// Text(text = mbti) } } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt new file mode 100644 index 0000000..2523467 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt @@ -0,0 +1,106 @@ +package com.sopt.now.compose.ui.signIn + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.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.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.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.sopt.now.compose.R +import com.sopt.now.compose.data.model.RequestSignInDto +import com.sopt.now.compose.ui.base.SoptInputTextField +import com.sopt.now.compose.ui.base.SoptOutlinedButton +import com.sopt.now.compose.ui.base.SoptPasswordTextField + +@Composable +fun SignInScreen( + onNavigateToHome: NavHostController, + onNavigateToSignUp: () -> Unit, + signInViewModel: SignInViewModel = viewModel(), +) { + val context = LocalContext.current + val signUpState by signInViewModel.signInState.collectAsState() + + var id by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + LaunchedEffect(signUpState) { + if (signUpState.isSuccess) { + Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() + onNavigateToHome.navigate("main") + } else if (signUpState.message.isNotBlank()) { + Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() + } + } + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + val isSignInButtonEnabled by remember(id, password) { + mutableStateOf( + id.isNotEmpty() && password.isNotEmpty() + ) + } + + Text( + text = stringResource(id = R.string.sign_in_screen_title), + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(16.dp) + ) + Column { + SoptInputTextField( + text = R.string.label_id, value = id, onValueChange = { id = it } + ) + SoptPasswordTextField( + text = R.string.label_pw, value = password, onValueChange = { password = it } + ) + } + + Column { + SoptOutlinedButton( + text = R.string.btn_sign_in, + onClick = { + if (isSignInButtonEnabled) { + signInViewModel.signIn( + RequestSignInDto( + authenticationId = id, + password = password, + ) + ) + } else { + Toast.makeText( + context, + "로그인 실패", + Toast.LENGTH_SHORT + ).show() + } + }, + enabled = true + ) + SoptOutlinedButton( + text = R.string.btn_sign_up, + onClick = onNavigateToSignUp, + enabled = true + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt new file mode 100644 index 0000000..a518399 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt @@ -0,0 +1,55 @@ +package com.sopt.now.compose.ui.signIn + +import androidx.lifecycle.ViewModel +import com.sopt.now.compose.data.model.RequestSignInDto +import com.sopt.now.compose.data.model.ResponseSignInDto +import com.sopt.now.compose.data.model.SignInState +import com.sopt.now.compose.data.module.ServicePool +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + + +class SignInViewModel : ViewModel() { + private val authService by lazy { ServicePool.authService } + + private val _signInState = MutableStateFlow(SignInState(isSuccess = false, message = "")) + val signInState = _signInState.asStateFlow() + + fun signIn(request: RequestSignInDto) { + authService.signIn(request).enqueue( + object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val userId = response.headers()["location"] + + _signInState.update { + SignInState( + isSuccess = true, + message = "유저 아이디는 $userId" + ) + } + } else { + val error = response.code() + _signInState.update { + SignInState( + isSuccess = false, + message = "로그인 실패 : $error" + ) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + _signInState.update { SignInState(isSuccess = false, message = "서버 에러") } + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt index e029021..0665f9d 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt @@ -117,7 +117,6 @@ fun SignUpScreen( phone = phone ) ) - onNavigateToSignIn.navigate("sign_in") } else { Toast.makeText( context, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bef4fda..0180cac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,13 +1,13 @@ NOW SOPT Android - - Welcome To SOPT! + + Welcome To SOPT! ID Password Nickname 전화번호 - 로그인 + 로그인 회원가입 From bc186b0fdb6a9e7b6ab9e62d4ef9da9a7ffe38f6 Mon Sep 17 00:00:00 2001 From: arinming Date: Fri, 3 May 2024 16:33:21 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[Feat]=20=ED=8C=94=EB=A1=9C=EC=9B=8C=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20API=20=EA=B5=AC=ED=98=84=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 3 + .../sopt/now/compose/data/model/Profile.kt | 2 +- .../now/compose/ui/home/HomeFriendProfile.kt | 9 +- .../sopt/now/compose/ui/home/HomeScreen.kt | 83 +++---------------- .../sopt/now/compose/ui/home/HomeViewModel.kt | 59 +++++++++++++ 5 files changed, 80 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index 63fdb13..19652a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -92,4 +92,7 @@ dependencies { // Glide implementation 'com.github.bumptech.glide:glide:4.13.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' + + // Coil + implementation 'io.coil-kt:coil-compose:2.6.0' } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/model/Profile.kt b/app/src/main/java/com/sopt/now/compose/data/model/Profile.kt index 9025131..d42f62e 100644 --- a/app/src/main/java/com/sopt/now/compose/data/model/Profile.kt +++ b/app/src/main/java/com/sopt/now/compose/data/model/Profile.kt @@ -1,7 +1,7 @@ package com.sopt.now.compose.data.model data class Profile( - val profileImage: Int, + val profileImage: String, val name: String, val description: String, ) diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeFriendProfile.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeFriendProfile.kt index 17617bf..4760514 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/home/HomeFriendProfile.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeFriendProfile.kt @@ -1,6 +1,5 @@ package com.sopt.now.compose.ui.home -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -14,12 +13,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage @Composable fun HomeFriendProfile( - profileImage: Int, + profileImage: String, name: String, description: String, ) { @@ -29,8 +28,8 @@ fun HomeFriendProfile( .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { - Image( - painter = painterResource(id = profileImage), + AsyncImage( + model = profileImage, contentDescription = null, modifier = Modifier .width(36.dp) diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt index 01371c3..ec787af 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt @@ -2,85 +2,28 @@ package com.sopt.now.compose.ui.home import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.viewmodel.compose.viewModel import com.sopt.now.compose.R -import com.sopt.now.compose.data.model.Profile @Composable -fun HomeScreen() { - val profileList: List = - listOf( - Profile( - profileImage = R.drawable.img_profile0, - name = "의피티", - description = "김아린 과제 마감 30분전" - ), - Profile( - profileImage = R.drawable.img_profile1, - name = "최준서", - description = "오운완 ㅋㅋ", - ), - Profile( - profileImage = R.drawable.img_profile2, - name = "이연진", - description = "아리니 넘 기엽당..", - ), - Profile( - profileImage = R.drawable.img_profile3, - name = "손민재", - description = "점심 뭐 먹지?", - ), - Profile( - profileImage = R.drawable.img_profile4, - name = "홍해인", - description = "난 눈물의 여왕이야", - ), - Profile( - profileImage = R.drawable.img_profile5, - name = "백현우", - description = "눈물의여왕시작하지말걸공부가안된다", - ), - Profile( - profileImage = R.drawable.img_profile6, - name = "이서경", - description = "저는 환연 과몰입러예요", - ), - Profile( - profileImage = R.drawable.img_profile7, - name = "이주원", - description = "너가 자기야 미안해 했잖아?", - ), - Profile( - profileImage = R.drawable.img_profile8, - name = "김광태", - description = "내일 뭐 해?", - ), - Profile( - profileImage = R.drawable.img_profile9, - name = "정현규", - description = "내봬누", - ), - Profile( - profileImage = R.drawable.img_profile10, - name = "성해은", - description = "벌써 스물 아홉이야", - ), - Profile( - profileImage = R.drawable.img_profile11, - name = "정규민", - description = "오마카세 사줄게", - ), - ) +fun HomeScreen(homeViewModel: HomeViewModel = viewModel()) { + val followerState by homeViewModel.followerState.collectAsState() LazyColumn(modifier = Modifier.fillMaxSize()) { - item { HomeMyProfile(R.drawable.img_arin, "김아린", "업보 청산의 끝이 보인다.") } - items(profileList.size) { index -> + item { + HomeMyProfile(R.drawable.img_arin, "김아린", "업보 청산의 끝이 보인다.") + } + items(followerState) { follower -> HomeFriendProfile( - profileImage = profileList[index].profileImage, - name = profileList[index].name, - description = profileList[index].description, + profileImage = follower.avatar, + name = "${follower.firstName} ${follower.lastName}", + description = follower.email, ) } } diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..91fc766 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt @@ -0,0 +1,59 @@ +package com.sopt.now.compose.ui.home + +import android.util.Log +import androidx.lifecycle.ViewModel +import com.sopt.now.compose.data.model.Profile +import com.sopt.now.compose.data.model.ResponseUserDto +import com.sopt.now.compose.data.model.UserData +import com.sopt.now.compose.data.module.ServicePool +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class HomeViewModel : ViewModel() { + private val followerService by lazy { ServicePool.followerService } + + private val _followerState = MutableStateFlow>(emptyList()) + val followerState = _followerState.asStateFlow() + + val friendList = mutableListOf() + + init { + fetchFollowerList() + } + + private fun fetchFollowerList() { + followerService.getUserList(page = 0).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val data = response.body()?.data + if (data != null) { + _followerState.value = data + mapFollowersToFriendList(data) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("HomeError", "${t.message}") + } + }) + } + + fun mapFollowersToFriendList(followers: List) { + for (follower in followers) { + friendList.add( + Profile( + profileImage = follower.avatar, + name = "${follower.firstName} ${follower.lastName}", + description = follower.email + ) + ) + } + } +} \ No newline at end of file From 72ce0d506b5479f71585d2664a91e6018cd18a09 Mon Sep 17 00:00:00 2001 From: arinming Date: Fri, 3 May 2024 16:48:54 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[Feat]=20=EB=A7=88=EC=9D=B4=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../compose/data/module/AuthInterceptor.kt | 2 +- .../now/compose/ui/myPage/MyPageScreen.kt | 19 ++++--- .../now/compose/ui/myPage/MyPageViewModel.kt | 53 +++++++++++++++++++ 3 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt diff --git a/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt b/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt index 83af933..eabf116 100644 --- a/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt +++ b/app/src/main/java/com/sopt/now/compose/data/module/AuthInterceptor.kt @@ -9,7 +9,7 @@ class AuthInterceptor : Interceptor { val originalRequest = chain.request() val newRequest = originalRequest.newBuilder() - .addHeader("memberId", "495") + .addHeader("memberId", "514") .build() return chain.proceed(newRequest) diff --git a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt index ba7dff2..e68a4b5 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageScreen.kt @@ -7,7 +7,10 @@ import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember @@ -15,10 +18,15 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import com.sopt.now.compose.R @Composable -fun MyPageScreen() { +fun MyPageScreen( + myPageViewModel: MyPageViewModel = viewModel(), +) { + val infoState by myPageViewModel.infoState.collectAsState() var offset by remember { mutableFloatStateOf(0f) } Column(modifier = Modifier @@ -36,11 +44,10 @@ fun MyPageScreen() { contentDescription = null, modifier = Modifier.aspectRatio(3f / 4f) ) - Column { -// Text(text = id) -// Text(text = pw) -// Text(text = nickname) -// Text(text = mbti) + Column(modifier = Modifier.padding(16.dp)) { + Text(text = infoState.data.authenticationId) + Text(text = infoState.data.nickname) + Text(text = infoState.data.phone) } } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt new file mode 100644 index 0000000..5a186e9 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt @@ -0,0 +1,53 @@ +package com.sopt.now.compose.ui.myPage + +import android.util.Log +import androidx.lifecycle.ViewModel +import com.sopt.now.compose.data.model.ResponseInfoDto +import com.sopt.now.compose.data.model.UserInfo +import com.sopt.now.compose.data.module.ServicePool +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class MyPageViewModel : ViewModel() { + private val authService by lazy { ServicePool.authService } + + private val _infoState = + MutableStateFlow( + ResponseInfoDto( + code = 0, + message = "", + data = UserInfo(authenticationId = "", nickname = "", phone = "") + ) + ) + val infoState = _infoState.asStateFlow() + + init { + fetchInfo() + } + + private fun fetchInfo() { + authService.memberInfo().enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val data = response.body() + if (data != null) { + _infoState.update { + data + } + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e("MyPageError", "${t.message}") + } + }) + } +} \ No newline at end of file From ec5b22de751f03c09aad9e8ba1e513ba1678c29d Mon Sep 17 00:00:00 2001 From: arinming Date: Thu, 23 May 2024 23:19:55 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[Refactor]=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?UI=20state=20=EA=B4=80=EB=A6=AC=20=EC=88=98=EC=A0=95=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 --- .../now/compose/data/model/ResponseInfoDto.kt | 4 +-- .../now/compose/data/model/ResponseUserDto.kt | 8 ++--- .../now/compose/data/module/ApiFactory.kt | 9 +++--- .../sopt/now/compose/ui/home/HomeViewModel.kt | 6 ++-- .../now/compose/ui/myPage/MyPageViewModel.kt | 13 +++++--- .../now/compose/ui/signIn/SignInScreen.kt | 31 ++++++++++--------- .../now/compose/ui/signIn/SignInViewModel.kt | 18 +++++++++-- .../now/compose/ui/signUp/SignUpScreen.kt | 27 ++++++++-------- .../now/compose/ui/signUp/SignUpViewModel.kt | 29 +++++++++++++++++ app/src/main/res/values/strings.xml | 6 ++++ 11 files changed, 104 insertions(+), 51 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 19652a1..96f1576 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,10 +89,6 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:logging-interceptor' - // Glide - implementation 'com.github.bumptech.glide:glide:4.13.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' - // Coil implementation 'io.coil-kt:coil-compose:2.6.0' } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt index 20cd109..c6164d2 100644 --- a/app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt +++ b/app/src/main/java/com/sopt/now/compose/data/model/ResponseInfoDto.kt @@ -10,11 +10,11 @@ data class ResponseInfoDto( @SerialName("message") val message: String, @SerialName("data") - val data: UserInfo, + val data: UserInfoDto, ) @Serializable -data class UserInfo( +data class UserInfoDto( @SerialName("authenticationId") val authenticationId: String, @SerialName("nickname") diff --git a/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt b/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt index 1093553..05a3591 100644 --- a/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt +++ b/app/src/main/java/com/sopt/now/compose/data/model/ResponseUserDto.kt @@ -9,12 +9,12 @@ data class ResponseUserDto( @SerialName("per_page") val perPage: Int, val total: Int, @SerialName("total_pages") val totalPages: Int, - val data: List, - val support: Support, + val data: List, + val support: SupportDto, ) @Serializable -data class UserData( +data class UserDataDto( val id: Int, val email: String, @SerialName("first_name") val firstName: String, @@ -23,7 +23,7 @@ data class UserData( ) @Serializable -data class Support( +data class SupportDto( val url: String, val text: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt b/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt index ec83f5e..b874bde 100644 --- a/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt +++ b/app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt @@ -8,6 +8,7 @@ import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import retrofit2.Retrofit +import retrofit2.create object ApiFactory { @@ -32,11 +33,11 @@ object ApiFactory { .build() } - inline fun createBase(): T = baseRetrofit.create(T::class.java) - inline fun createFollower(): T = followerRetrofit.create(T::class.java) + inline fun createBaseRetrofit(): T = baseRetrofit.create() + inline fun createFollowerRetrofit(): T = followerRetrofit.create() } object ServicePool { - val authService = ApiFactory.createBase() - val followerService = ApiFactory.createFollower() + val authService = ApiFactory.createBaseRetrofit() + val followerService = ApiFactory.createFollowerRetrofit() } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt index 91fc766..a79e0bb 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt @@ -4,7 +4,7 @@ import android.util.Log import androidx.lifecycle.ViewModel import com.sopt.now.compose.data.model.Profile import com.sopt.now.compose.data.model.ResponseUserDto -import com.sopt.now.compose.data.model.UserData +import com.sopt.now.compose.data.model.UserDataDto import com.sopt.now.compose.data.module.ServicePool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -15,7 +15,7 @@ import retrofit2.Response class HomeViewModel : ViewModel() { private val followerService by lazy { ServicePool.followerService } - private val _followerState = MutableStateFlow>(emptyList()) + private val _followerState = MutableStateFlow>(emptyList()) val followerState = _followerState.asStateFlow() val friendList = mutableListOf() @@ -45,7 +45,7 @@ class HomeViewModel : ViewModel() { }) } - fun mapFollowersToFriendList(followers: List) { + fun mapFollowersToFriendList(followers: List) { for (follower in followers) { friendList.add( Profile( diff --git a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt index 5a186e9..4a773bc 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt @@ -1,9 +1,8 @@ package com.sopt.now.compose.ui.myPage -import android.util.Log import androidx.lifecycle.ViewModel import com.sopt.now.compose.data.model.ResponseInfoDto -import com.sopt.now.compose.data.model.UserInfo +import com.sopt.now.compose.data.model.UserInfoDto import com.sopt.now.compose.data.module.ServicePool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -20,7 +19,7 @@ class MyPageViewModel : ViewModel() { ResponseInfoDto( code = 0, message = "", - data = UserInfo(authenticationId = "", nickname = "", phone = "") + data = UserInfoDto(authenticationId = "", nickname = "", phone = "") ) ) val infoState = _infoState.asStateFlow() @@ -46,7 +45,13 @@ class MyPageViewModel : ViewModel() { } override fun onFailure(call: Call, t: Throwable) { - Log.e("MyPageError", "${t.message}") + _infoState.update { + ResponseInfoDto( + code = -1, + message = "${t.message}", + data = UserInfoDto(authenticationId = "", nickname = "", phone = "") + ) + } } }) } diff --git a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt index 2523467..1fd0b28 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.collectAsState 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 @@ -35,17 +34,17 @@ fun SignInScreen( signInViewModel: SignInViewModel = viewModel(), ) { val context = LocalContext.current - val signUpState by signInViewModel.signInState.collectAsState() + val signInState by signInViewModel.signInState.collectAsState() - var id by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } + val id by signInViewModel.id.collectAsState() + val password by signInViewModel.password.collectAsState() - LaunchedEffect(signUpState) { - if (signUpState.isSuccess) { - Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() - onNavigateToHome.navigate("main") - } else if (signUpState.message.isNotBlank()) { - Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() + LaunchedEffect(signInState) { + if (signInState.isSuccess) { + Toast.makeText(context, signInState.message, Toast.LENGTH_SHORT).show() + onNavigateToHome.navigate(context.getString(R.string.route_main)) + } else if (signInState.message.isNotBlank()) { + Toast.makeText(context, signInState.message, Toast.LENGTH_SHORT).show() } } @@ -68,10 +67,14 @@ fun SignInScreen( ) Column { SoptInputTextField( - text = R.string.label_id, value = id, onValueChange = { id = it } + text = R.string.label_id, + value = id, + onValueChange = { signInViewModel.updateId(it) } ) SoptPasswordTextField( - text = R.string.label_pw, value = password, onValueChange = { password = it } + text = R.string.label_pw, + value = password, + onValueChange = { signInViewModel.updatePassword(it) } ) } @@ -89,7 +92,7 @@ fun SignInScreen( } else { Toast.makeText( context, - "로그인 실패", + R.string.sign_up_fail, Toast.LENGTH_SHORT ).show() } @@ -103,4 +106,4 @@ fun SignInScreen( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt index a518399..42f9c72 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt @@ -12,13 +12,27 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response - class SignInViewModel : ViewModel() { private val authService by lazy { ServicePool.authService } private val _signInState = MutableStateFlow(SignInState(isSuccess = false, message = "")) val signInState = _signInState.asStateFlow() + private val _id = MutableStateFlow("") + val id = _id.asStateFlow() + + private val _password = MutableStateFlow("") + val password = _password.asStateFlow() + + fun updateId(newId: String) { + _id.value = newId + } + + fun updatePassword(newPassword: String) { + _password.value = newPassword + } + + fun signIn(request: RequestSignInDto) { authService.signIn(request).enqueue( object : Callback { @@ -52,4 +66,4 @@ class SignInViewModel : ViewModel() { } ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt index 0665f9d..ae4a3e9 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -44,15 +43,15 @@ fun SignUpScreen( val context = LocalContext.current val signUpState by signUpViewModel.signUpState.collectAsState() - var id by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } - var nickname by remember { mutableStateOf("") } - var phone by remember { mutableStateOf("") } + val id by signUpViewModel.id.collectAsState() + val password by signUpViewModel.password.collectAsState() + val nickname by signUpViewModel.nickname.collectAsState() + val phoneNumber by signUpViewModel.phoneNumber.collectAsState() LaunchedEffect(signUpState) { if (signUpState.isSuccess) { Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() - onNavigateToSignIn.navigate("sign_in") + onNavigateToSignIn.navigate(context.getString(R.string.route_sign_in)) } else if (signUpState.message.isNotBlank()) { Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() } @@ -73,11 +72,11 @@ fun SignUpScreen( } } - val isSignUpButtonEnabled by remember(id, password, nickname, phone) { + val isSignUpButtonEnabled by remember(id, password, nickname, phoneNumber) { mutableStateOf( id.length in 6..10 && password.length in 8..12 && nickname.isNotEmpty() && !nickname.contains( " " - ) && phone.matches(Regex("^010-\\d{4}-\\d{4}\$")) + ) && phoneNumber.matches(Regex("^010-\\d{4}-\\d{4}\$")) ) } @@ -92,19 +91,19 @@ fun SignUpScreen( SoptInputTextField( text = R.string.hint_id, value = id, - onValueChange = { id = it }) + onValueChange = { signUpViewModel.updateId(it) }) SoptPasswordTextField( text = R.string.hint_pw, value = password, - onValueChange = { password = it }) + onValueChange = { signUpViewModel.updatePassword(it) }) SoptInputTextField( text = R.string.hint_nickname, value = nickname, - onValueChange = { nickname = it }) + onValueChange = { signUpViewModel.updateNickname(it) }) SoptInputTextField( text = R.string.hint_phone, - value = phone, - onValueChange = { phone = it }) + value = phoneNumber, + onValueChange = { signUpViewModel.updatePhoneNumber(it) }) } } SoptOutlinedButton(text = R.string.btn_sign_up, onClick = { @@ -114,7 +113,7 @@ fun SignUpScreen( authenticationId = id, password = password, nickname = nickname, - phone = phone + phone = phoneNumber ) ) } else { diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt index d0f71c8..b197b52 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt @@ -20,6 +20,35 @@ class SignUpViewModel : ViewModel() { private val _signUpState = MutableStateFlow(SignUpState(isSuccess = false, message = "")) val signUpState = _signUpState.asStateFlow() + private val _id = MutableStateFlow("") + val id = _id.asStateFlow() + + private val _password = MutableStateFlow("") + val password = _password.asStateFlow() + + private val _nickname = MutableStateFlow("") + val nickname = _nickname.asStateFlow() + + private val _phoneNumber = MutableStateFlow("") + val phoneNumber = _phoneNumber.asStateFlow() + + fun updateId(id: String) { + _id.value = id + } + + fun updatePassword(password: String) { + _password.value = password + } + + fun updateNickname(nickname: String) { + _nickname.value = nickname + } + + fun updatePhoneNumber(phoneNumber: String) { + _phoneNumber.value = phoneNumber + } + + fun signUp(request: RequestSignUpDto) { authService.signUp(request).enqueue( object : Callback { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0180cac..5f51e8c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ 전화번호 로그인 회원가입 + 로그인 실패 Sign Up @@ -22,4 +23,9 @@ 검색 마이페이지 + + main + sign_in + sign_up + \ No newline at end of file From fb2b30d64e9f10478075cb3796780568ec6e16df Mon Sep 17 00:00:00 2001 From: arinming Date: Thu, 23 May 2024 23:37:59 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[Refactor]=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?Side=20Effect=20=EC=B6=94=EA=B0=80=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../now/compose/ui/signUp/SignUpScreen.kt | 33 ++++++++++++++----- .../now/compose/ui/signUp/SignUpSideEffect.kt | 6 ++++ .../now/compose/ui/signUp/SignUpViewModel.kt | 33 +++++++------------ app/src/main/res/values/strings.xml | 1 + 4 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt index ae4a3e9..5596261 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt @@ -33,6 +33,10 @@ import com.sopt.now.compose.data.model.RequestSignUpDto import com.sopt.now.compose.ui.base.SoptInputTextField import com.sopt.now.compose.ui.base.SoptOutlinedButton import com.sopt.now.compose.ui.base.SoptPasswordTextField +import com.sopt.now.compose.ui.signUp.SignUpViewModel.Companion.MAX_LENGTH_LOGIN +import com.sopt.now.compose.ui.signUp.SignUpViewModel.Companion.MAX_LENGTH_PASSWORD +import com.sopt.now.compose.ui.signUp.SignUpViewModel.Companion.MIN_LENGTH_LOGIN +import com.sopt.now.compose.ui.signUp.SignUpViewModel.Companion.MIN_LENGTH_PASSWORD import kotlinx.coroutines.launch @Composable @@ -41,22 +45,29 @@ fun SignUpScreen( signUpViewModel: SignUpViewModel = viewModel(), ) { val context = LocalContext.current - val signUpState by signUpViewModel.signUpState.collectAsState() + val signUpEvent by signUpViewModel.signUpEvent.collectAsState() val id by signUpViewModel.id.collectAsState() val password by signUpViewModel.password.collectAsState() val nickname by signUpViewModel.nickname.collectAsState() val phoneNumber by signUpViewModel.phoneNumber.collectAsState() - LaunchedEffect(signUpState) { - if (signUpState.isSuccess) { - Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() - onNavigateToSignIn.navigate(context.getString(R.string.route_sign_in)) - } else if (signUpState.message.isNotBlank()) { - Toast.makeText(context, signUpState.message, Toast.LENGTH_SHORT).show() + LaunchedEffect(signUpEvent) { + when (val event = signUpEvent) { + is SignUpSideEffect.Success -> { + Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() + onNavigateToSignIn.navigate(context.getString(R.string.route_sign_in)) + } + + is SignUpSideEffect.Error -> { + Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() + } + + else -> {} } } + Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -74,7 +85,9 @@ fun SignUpScreen( val isSignUpButtonEnabled by remember(id, password, nickname, phoneNumber) { mutableStateOf( - id.length in 6..10 && password.length in 8..12 && nickname.isNotEmpty() && !nickname.contains( + id.length in MIN_LENGTH_LOGIN..MAX_LENGTH_LOGIN + && password.length in MIN_LENGTH_PASSWORD..MAX_LENGTH_PASSWORD + && nickname.isNotEmpty() && !nickname.contains( " " ) && phoneNumber.matches(Regex("^010-\\d{4}-\\d{4}\$")) ) @@ -119,10 +132,12 @@ fun SignUpScreen( } else { Toast.makeText( context, - "조건을 만족하지 않습니다", + R.string.sign_up_fail_message, Toast.LENGTH_SHORT ).show() } }, enabled = true) } } + + diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt new file mode 100644 index 0000000..6d2bbd4 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt @@ -0,0 +1,6 @@ +package com.sopt.now.compose.ui.signUp + +sealed class SignUpSideEffect { + data class Success(val message: String) : SignUpSideEffect() + data class Error(val message: String) : SignUpSideEffect() +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt index b197b52..f8c0020 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt @@ -1,14 +1,11 @@ package com.sopt.now.compose.ui.signUp -import android.util.Log import androidx.lifecycle.ViewModel import com.sopt.now.compose.data.model.RequestSignUpDto import com.sopt.now.compose.data.model.ResponseSignUpDto -import com.sopt.now.compose.data.model.SignUpState import com.sopt.now.compose.data.module.ServicePool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -17,8 +14,8 @@ import retrofit2.Response class SignUpViewModel : ViewModel() { private val authService by lazy { ServicePool.authService } - private val _signUpState = MutableStateFlow(SignUpState(isSuccess = false, message = "")) - val signUpState = _signUpState.asStateFlow() + private val _signUpEvent = MutableStateFlow(null) + val signUpEvent = _signUpEvent.asStateFlow() private val _id = MutableStateFlow("") val id = _id.asStateFlow() @@ -58,30 +55,24 @@ class SignUpViewModel : ViewModel() { ) { if (response.isSuccessful) { val userId = response.headers()["location"] - _signUpState.update { - SignUpState( - isSuccess = true, - message = "가입된 유저 아이디는 $userId" - ) - } + _signUpEvent.value = SignUpSideEffect.Success("가입된 유저 아이디는 $userId") } else { val error = response.code() - _signUpState.update { - SignUpState( - isSuccess = false, - message = "회원가입 실패 : $error" - ) - } + _signUpEvent.value = SignUpSideEffect.Error("회원가입 실패 : $error") } } override fun onFailure(call: Call, t: Throwable) { - _signUpState.update { - SignUpState(isSuccess = false, message = "서버 에러") - } - Log.d("SignUp", "${t.message}") + _signUpEvent.value = SignUpSideEffect.Error("서버 에러: ${t.message}") } }, ) } + + companion object { + const val MIN_LENGTH_LOGIN = 6 + const val MAX_LENGTH_LOGIN = 10 + const val MIN_LENGTH_PASSWORD = 8 + const val MAX_LENGTH_PASSWORD = 12 + } } \ 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 5f51e8c..88746f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,7 @@ 로그인 회원가입 로그인 실패 + 조건을 만족하지 않습니다 Sign Up From d689df094b300e19fad2a799abf1e0d35fdb1d8a Mon Sep 17 00:00:00 2001 From: arinming Date: Fri, 24 May 2024 17:03:34 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[Refactor]=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?Side=20Effect=20Loading=20=EC=B6=94=EA=B0=80=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 8 ++++---- .../java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt | 4 ++++ .../com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 96f1576..f0ea816 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,8 +54,8 @@ android { } dependencies { - implementation 'androidx.core:core-ktx:1.13.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.0' implementation 'androidx.activity:activity-compose:1.9.0' implementation platform('androidx.compose:compose-bom:2024.05.00') implementation 'androidx.compose.ui:ui' @@ -73,10 +73,10 @@ dependencies { implementation 'androidx.compose.material:material:1.6.7' // Lifecycle Viewmodel - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0' // Fragment && Activity - implementation 'androidx.fragment:fragment-ktx:1.6.2' + implementation 'androidx.fragment:fragment-ktx:1.7.1' implementation 'androidx.activity:activity-ktx:1.9.0' // Retrofit diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt index 5596261..8fc48dd 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt @@ -59,6 +59,10 @@ fun SignUpScreen( onNavigateToSignIn.navigate(context.getString(R.string.route_sign_in)) } + is SignUpSideEffect.Loading -> { + Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() + } + is SignUpSideEffect.Error -> { Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt index 6d2bbd4..cd1cba6 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt @@ -2,5 +2,6 @@ package com.sopt.now.compose.ui.signUp sealed class SignUpSideEffect { data class Success(val message: String) : SignUpSideEffect() + data class Loading(val message: String) : SignUpSideEffect() data class Error(val message: String) : SignUpSideEffect() } \ No newline at end of file