Skip to content
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/#17] week7-compose / 필수과제 #19

Open
wants to merge 6 commits into
base: develop-compose
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions app/src/main/java/com/sopt/now/compose/AuthService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,19 @@ import com.sopt.now.data.model.RequestSignUpDto
import com.sopt.now.data.model.ResponseLoginDto
import com.sopt.now.data.model.ResponseSignUpDto
import com.sopt.now.data.model.ResponseUserInfoDto
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST

interface AuthService {
@POST("member/join")
fun signUp(
@Body request: RequestSignUpDto
): Call<ResponseSignUpDto>
suspend fun signUp(@Body signUpDto: RequestSignUpDto): Response<ResponseSignUpDto>

@POST("/member/login")
fun login(
@Body request: RequestLoginDto
): Call<ResponseLoginDto>
suspend fun login(@Body loginDto: RequestLoginDto): Response<ResponseLoginDto>


@GET("/member/info")
fun getUserInfo(@Header("memberId") memberId: String): Call<ResponseUserInfoDto>
suspend fun getUserInfo(): Response<ResponseUserInfoDto>
}
14 changes: 11 additions & 3 deletions app/src/main/java/com/sopt/now/compose/MemberIdInterceptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ object MemberIdInterceptor : Interceptor {
private set

override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var request = chain.request()

// 요청에 MemberID 헤더 추가
memberId?.let {
request = request.newBuilder()
.addHeader("MemberID", it)
.build()
}

val response = chain.proceed(request)

// 응답 헤더에서 Location 값 추출해서 memberId에 저장 때리기

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 때리기 삭제한 거 웃기네

// 응답 헤더에서 Location 값 추출해서 memberId에 저장
response.header("Location")?.let {
memberId = it
}

return response
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sopt.now.compose.repository

import com.sopt.now.data.model.RequestLoginDto
import com.sopt.now.data.model.ResponseLoginDto

interface LoginRepository {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

레포지토리 다 나눈 거 깔끔하네요-!!

suspend fun login(loginDto: RequestLoginDto): Result<ResponseLoginDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sopt.now.compose.repository

import com.sopt.now.data.model.RequestSignUpDto
import com.sopt.now.data.model.ResponseSignUpDto

interface SignUpRepository {
suspend fun signUp(signUpDto: RequestSignUpDto): Result<ResponseSignUpDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sopt.now.compose.repository

import com.sopt.now.data.model.ResponseUserInfoDto

interface UserInfoRepository {
suspend fun getUserInfo(): Result<ResponseUserInfoDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.sopt.now.compose.repositoryimpl

import com.sopt.now.AuthService
import com.sopt.now.compose.repository.LoginRepository
import com.sopt.now.data.model.RequestLoginDto
import com.sopt.now.data.model.ResponseLoginDto


class LoginRepositoryImpl(
private val authService: AuthService
) : LoginRepository {
override suspend fun login(loginDto: RequestLoginDto): Result<ResponseLoginDto> {
return runCatching {
val response = authService.login(loginDto)
if (response.isSuccessful) {
response.body()?.let {
Result.success(it)
} ?: Result.failure(Exception("Body is null"))
} else {
Result.failure(Exception(response.errorBody()?.string()))
}
}.getOrElse {
Result.failure(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.sopt.now.compose.repositoryimpl

import com.sopt.now.AuthService
import com.sopt.now.compose.repository.SignUpRepository
import com.sopt.now.data.model.RequestSignUpDto
import com.sopt.now.data.model.ResponseSignUpDto

class SignUpRepositoryImpl(
private val authService: AuthService
) : SignUpRepository {
override suspend fun signUp(signUpDto: RequestSignUpDto): Result<ResponseSignUpDto> {
return runCatching {
val response = authService.signUp(signUpDto)
if (response.isSuccessful) {
response.body()?.let {
Result.success(it)
} ?: Result.failure(Exception("Body is null"))
} else {
Result.failure(Exception(response.errorBody()?.string()))
}
}.getOrElse {
Result.failure(it)
}
Comment on lines +20 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getOrElse는 어떤 기능을 하나용..?

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.sopt.now.compose.repositoryimpl

import com.sopt.now.AuthService
import com.sopt.now.compose.repository.UserInfoRepository
import com.sopt.now.data.model.ResponseUserInfoDto

class UserInfoRepositoryImpl(
private val authService: AuthService
) : UserInfoRepository {
override suspend fun getUserInfo(): Result<ResponseUserInfoDto> {
return runCatching {
val response = authService.getUserInfo()
if (response.isSuccessful) {
response.body()?.let {
Result.success(it)
} ?: Result.failure(Exception("Body is null"))
} else {
Result.failure(Exception(response.errorBody()?.string()))
}
}.getOrElse {
Result.failure(it)
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sopt.now.compose.screen.auth.login

import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -25,21 +24,27 @@ 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.ServicePool
import com.sopt.now.compose.R
import com.sopt.now.compose.component.ActionButton
import com.sopt.now.compose.component.InputField
import com.sopt.now.compose.navigation.NavRoutes
import com.sopt.now.compose.repositoryimpl.LoginRepositoryImpl
import com.sopt.now.compose.utils.ToastUtil.toast
import com.sopt.now.data.model.RequestLoginDto

@Composable
fun LoginScreen(
navController: NavHostController
) {
val viewModel: LoginViewModel = viewModel()
val context = LocalContext.current
val authService = ServicePool.authService
val loginRepository = LoginRepositoryImpl(authService)
val viewModel: LoginViewModel = viewModel(factory = LoginViewModelFactory(loginRepository))

var userId by rememberSaveable { mutableStateOf("") }
var userPwd by rememberSaveable { mutableStateOf("") }
val loginState = viewModel.loginState.observeAsState()
val context = LocalContext.current // to use ToastMessage
val loginState by viewModel.loginState.observeAsState()

Column(modifier = Modifier.padding(16.dp)) {
LoginTitle()
Expand All @@ -58,19 +63,19 @@ fun LoginScreen(
isPassword = true
)

when (val state = loginState.value) {
when (val state = loginState) {
is LoginState.Loading -> CircularProgressIndicator()
is LoginState.Success -> {
LaunchedEffect(state.message) {
Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
toast(context, state.message)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 쓰니까 훨씬 깔끔하네요! 배우고 갑니당

navController.navigate(NavRoutes.HOME) {
popUpTo(NavRoutes.LOGIN) { inclusive = true }
}
}
}
is LoginState.Error -> {
LaunchedEffect(state.message) {
Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
toast(context, state.message)
}
ActionButton(
text = stringResource(id = R.string.login_button_signin),
Expand All @@ -97,6 +102,7 @@ fun LoginScreen(
}
}


@Composable
fun LoginTitle() {
Column(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,26 @@ package com.sopt.now.compose.screen.auth.login
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.sopt.now.ServicePool
import com.sopt.now.compose.MemberIdInterceptor
import androidx.lifecycle.viewModelScope
import com.sopt.now.compose.repository.LoginRepository
import com.sopt.now.data.model.RequestLoginDto
import com.sopt.now.data.model.ResponseLoginDto
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.coroutines.launch

class LoginViewModel : ViewModel() {
class LoginViewModel(
private val loginRepository: LoginRepository
) : ViewModel() {
private val _loginState = MutableLiveData<LoginState>()
val loginState: LiveData<LoginState> = _loginState

fun login(dto: RequestLoginDto) {
_loginState.value = LoginState.Loading
val call = ServicePool.authService.login(dto)
call.enqueue(object : Callback<ResponseLoginDto> {
override fun onResponse(call: Call<ResponseLoginDto>, response: Response<ResponseLoginDto>) {
if (response.isSuccessful && response.body()?.code == 200) {
val successMessage = "로그인 성공: Member ID = ${MemberIdInterceptor.memberId}"
_loginState.value = LoginState.Success(successMessage)
} else {
_loginState.value = LoginState.Error(response.body()?.message ?: "로그인 실패")
}
fun login(loginDto: RequestLoginDto) {
viewModelScope.launch {
_loginState.value = LoginState.Loading
val result = loginRepository.login(loginDto)
_loginState.value = if (result.isSuccess) {
LoginState.Success("로그인 성공")
} else {
LoginState.Error(result.exceptionOrNull()?.message ?: "로그인 실패")
}

override fun onFailure(call: Call<ResponseLoginDto>, t: Throwable) {
_loginState.value = LoginState.Error("네트워크 오류: ${t.message}")
}
})
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.sopt.now.compose.screen.auth.login

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.sopt.now.compose.repository.LoginRepository

class LoginViewModelFactory(private val loginRepository: LoginRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return LoginViewModel(loginRepository) as T
}
throw IllegalArgumentException("알 수 없는 ViewModel 클래스")
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sopt.now.compose.screen.auth.signup

import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -11,6 +10,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -22,18 +22,26 @@ 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.ServicePool
import com.sopt.now.compose.R
import com.sopt.now.compose.component.UserInfoInputField
import com.sopt.now.compose.navigation.NavRoutes
import com.sopt.now.compose.repositoryimpl.SignUpRepositoryImpl
import com.sopt.now.compose.utils.ToastUtil.toast
import com.sopt.now.compose.utils.rememberUserInfoState
import com.sopt.now.data.model.RequestSignUpDto

@Composable
fun SignUpScreen(navController: NavHostController) {
val viewModel: SignUpViewModel = viewModel()
val userInfoState = rememberUserInfoState()
val signUpState = viewModel.signUpState.observeAsState()
fun SignUpScreen(
navController: NavHostController
) {
val context = LocalContext.current // to use ToastMessage
val authService = ServicePool.authService
val signUpRepository = SignUpRepositoryImpl(authService)
val viewModel: SignUpViewModel = viewModel(factory = SignUpViewModelFactory(signUpRepository))

val userInfoState = rememberUserInfoState()
val signUpState by viewModel.signUpState.observeAsState()


Column(modifier = Modifier.padding(16.dp)) {
Expand Down Expand Up @@ -67,19 +75,19 @@ fun SignUpScreen(navController: NavHostController) {

Spacer(modifier = Modifier.height(16.dp))

when (val state = signUpState.value) {
when (signUpState) {
is SignUpState.Loading -> CircularProgressIndicator()
is SignUpState.Success -> {
LaunchedEffect(state.message) {
Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
LaunchedEffect(true) {
toast(context, (signUpState as SignUpState.Success).message)
navController.navigate(NavRoutes.LOGIN) {
popUpTo(NavRoutes.SIGN_UP) { inclusive = true }
}
}
}
is SignUpState.Error -> {
LaunchedEffect(state.message) {
Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
LaunchedEffect(true) {
toast(context, (signUpState as SignUpState.Error).message)
}
}
null -> {
Expand All @@ -104,6 +112,7 @@ fun SignUpScreen(navController: NavHostController) {
}
}


@Composable
fun TitleSection(title: String) {
Column(
Expand Down
Loading