-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] Week4 필수 과제 #9
base: develop
Are you sure you want to change the base?
Changes from all commits
cee8105
3aad6ec
37c44af
ef08fbd
1445650
ac5a610
381a434
d1497cd
1ed9d96
e087cb2
09cb792
a61fc26
a29a3e1
07beb20
0c77d44
c8b5b52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.sopt.and.data | ||
|
||
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.data.network.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 <reified T> create(): T = retrofit.create(T::class.java) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음 보는 코드여서 어떤 코드인지 찾아봤는데 재사용성이 더 좋아진다고하니 좋은 코드인 것 같아요~! |
||
} | ||
|
||
object ServicePool { | ||
val userService = ApiFactory.create<UserService>() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.sopt.and.data.model.request | ||
|
||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class UserLoginRequest( | ||
@SerialName("username") | ||
val username: String, | ||
@SerialName("password") | ||
val password: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package org.sopt.and.data.model.request | ||
|
||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class UserSignUpRequest( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dto 네이밍 형식을 서로 맞춰주면 좋을 것 같아요! |
||
@SerialName("username") | ||
val username: String, | ||
@SerialName("password") | ||
val password: String, | ||
@SerialName("hobby") | ||
val hobby: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.sopt.and.data.model.response | ||
|
||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class ResponseUserHobbyDto( | ||
@SerialName("result") | ||
val result: Result | ||
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 형태가 계속 반복되니 baseResponse를 사용해봐도 좋을 듯 합니다. |
||
) { | ||
@Serializable | ||
data class Result( | ||
@SerialName("hobby") | ||
val hobby: String | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.sopt.and.data.model.response | ||
|
||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class ResponseUserSignUpDto( | ||
@SerialName("result") | ||
val result: Result | ||
) { | ||
@Serializable | ||
data class Result( | ||
@SerialName("no") | ||
val no: Int | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.sopt.and.data.model.response | ||
|
||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class ResponseUserTokenDto( | ||
@SerialName("result") | ||
val result: Result | ||
) { | ||
@Serializable | ||
data class Result( | ||
@SerialName("token") | ||
val token: String | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package org.sopt.and.data.network.service | ||
|
||
import org.sopt.and.data.model.response.ResponseUserHobbyDto | ||
import org.sopt.and.data.model.response.ResponseUserSignUpDto | ||
import org.sopt.and.data.model.response.ResponseUserTokenDto | ||
import org.sopt.and.data.model.request.UserLoginRequest | ||
import org.sopt.and.data.model.request.UserSignUpRequest | ||
import retrofit2.Call | ||
import retrofit2.http.Body | ||
import retrofit2.http.GET | ||
import retrofit2.http.Header | ||
import retrofit2.http.POST | ||
|
||
interface UserService { | ||
@POST("/user") | ||
fun postUserSignUp( | ||
@Body body: UserSignUpRequest | ||
): Call<ResponseUserSignUpDto> | ||
|
||
@POST("/login") | ||
fun postUserLogin( | ||
@Body body: UserLoginRequest | ||
): Call<ResponseUserTokenDto> | ||
|
||
@GET("/user/my-hobby") | ||
fun getUserHobby( | ||
@Header("token") token: String, | ||
): Call<ResponseUserHobbyDto> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package org.sopt.and.feature.login | ||
|
||
import android.content.Context | ||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner | ||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.background | ||
|
@@ -24,8 +25,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api | |
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.SnackbarHost | ||
import androidx.compose.material3.SnackbarHostState | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TopAppBarDefaults | ||
import androidx.compose.material3.VerticalDivider | ||
|
@@ -34,7 +33,6 @@ import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.livedata.observeAsState | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.rememberCoroutineScope | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.focus.FocusDirection | ||
|
@@ -51,36 +49,35 @@ import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | ||
import androidx.lifecycle.viewmodel.compose.viewModel | ||
import androidx.navigation.NavController | ||
import kotlinx.coroutines.launch | ||
import org.sopt.and.R | ||
import org.sopt.and.core.designsystem.component.AuthTextField | ||
import org.sopt.and.core.designsystem.component.SocialLoginButtonGroup | ||
import org.sopt.and.core.designsystem.component.WavveLoginButton | ||
import org.sopt.and.data.model.request.UserLoginRequest | ||
import org.sopt.and.feature.main.Routes | ||
import org.sopt.and.ui.theme.WavveTheme | ||
import org.sopt.and.utils.noRippleClickable | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Composable | ||
fun LoginScreen( | ||
localEmail: String, | ||
localPassword: String, | ||
navController: NavController, | ||
onLoginSuccess: (String, String) -> Unit, | ||
viewModel: LoginViewModel = viewModel() | ||
) { | ||
val context = LocalContext.current | ||
val focusManager = LocalFocusManager.current | ||
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher | ||
val sharedPreferences = context.getSharedPreferences("token", Context.MODE_PRIVATE) | ||
Comment on lines
70
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !! 사용은 지양합시다 NPE가 발생할 수 있서요 |
||
val editor = sharedPreferences.edit() | ||
|
||
val scope = rememberCoroutineScope() | ||
val snackbarHostState = remember { SnackbarHostState() } | ||
|
||
val email by viewModel.email.observeAsState("") | ||
val userName by viewModel.userName.observeAsState("") | ||
val password by viewModel.password.observeAsState("") | ||
val showPassword = remember { mutableStateOf(false) } | ||
|
||
Scaffold(modifier = Modifier.fillMaxSize(), | ||
Scaffold( | ||
modifier = Modifier.fillMaxSize(), | ||
topBar = { | ||
CenterAlignedTopAppBar( | ||
title = { | ||
|
@@ -109,11 +106,6 @@ fun LoginScreen( | |
colors = TopAppBarDefaults.mediumTopAppBarColors(containerColor = WavveTheme.colors.BackgroundGray) | ||
) | ||
}, | ||
snackbarHost = { | ||
SnackbarHost( | ||
hostState = snackbarHostState | ||
) | ||
} | ||
) { innerPadding -> | ||
Box( | ||
modifier = Modifier | ||
|
@@ -131,11 +123,11 @@ fun LoginScreen( | |
AuthTextField( | ||
modifier = Modifier | ||
.fillMaxWidth(), | ||
value = email, | ||
value = userName, | ||
onValueChange = { | ||
viewModel.setEmail(it) | ||
viewModel.setUserName(it) | ||
}, | ||
placeholder = stringResource(R.string.email_or_id), | ||
placeholder = stringResource(R.string.placeholder_user_name), | ||
keyboardOptions = KeyboardOptions.Default.copy( | ||
keyboardType = KeyboardType.Email, | ||
imeAction = ImeAction.Next | ||
|
@@ -184,16 +176,19 @@ fun LoginScreen( | |
WavveLoginButton( | ||
onClick = { | ||
viewModel.onLoginClick( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. viewModel의 함수가 중첩돼서 가시성이 조금 떨어지는 것 같아요! |
||
localEmail = localEmail, | ||
localPassword = localPassword, | ||
onSuccess = { email, password -> | ||
onLoginSuccess(email, password) | ||
}, | ||
onFailure = { | ||
scope.launch { | ||
snackbarHostState.showSnackbar(context.getString(R.string.fail_to_login)) | ||
onSuccess = { userName, password -> | ||
viewModel.postUserLogin( | ||
context = context, | ||
body = UserLoginRequest( | ||
username = userName, | ||
password = password | ||
), | ||
) { body -> | ||
editor.putString("loginToken", body!!.result.token) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 스트링 추출 해볼까요! |
||
editor.apply() | ||
onLoginSuccess(userName, password) | ||
} | ||
} | ||
}, | ||
) | ||
|
||
//키보드 내리기 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lazy 하게 설정한 것 좋은 것 같아요!