diff --git a/app/build.gradle b/app/build.gradle index 81ec450..ddfcb61 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,6 +38,8 @@ android { } dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") + implementation 'com.jakewharton.timber:timber:5.0.1' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" // ViewModel 생성함수를 편하게 사용하고 싶다면? implementation "androidx.fragment:fragment-ktx:1.5.3" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4cdf4a1..3cc219b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:theme="@style/Theme.INSOPTAndroidPractice" tools:targetApi="31"> diff --git a/app/src/main/java/org/sopt/sample/HomeFragment.kt b/app/src/main/java/org/sopt/sample/HomeFragment.kt index a5e978d..78becf4 100644 --- a/app/src/main/java/org/sopt/sample/HomeFragment.kt +++ b/app/src/main/java/org/sopt/sample/HomeFragment.kt @@ -1,46 +1,33 @@ package org.sopt.sample -import org.sopt.sample.adapter.rvAdapter -import org.sopt.sample.databinding.FragmentHomeBinding import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.google.android.material.snackbar.Snackbar +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import org.sopt.sample.adapter.rvAdapter import org.sopt.sample.base.BindingFragment -import org.sopt.sample.databinding.FragmentSearchBinding -import org.sopt.sample.remote.* -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import org.sopt.sample.databinding.FragmentHomeBinding +import org.sopt.sample.user.UserRepository +import org.sopt.sample.user.UserViewModel +import org.sopt.sample.user.UserViewModelFactory -class HomeFragment: BindingFragment(R.layout.fragment_home) { - private var _binding : FragmentHomeBinding? = null - get() = requireNotNull(_binding) - private val userService = UserServicePool.userService +class HomeFragment : BindingFragment(R.layout.fragment_home) { + private lateinit var viewModel: UserViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewModel = ViewModelProvider(this, UserViewModelFactory(UserRepository())) + .get(UserViewModel::class.java) + binding.viewModel = viewModel + binding.lifecycleOwner = this + val adapter = rvAdapter(requireContext()) binding.rvRepos.adapter = adapter - userService.user().enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - Log.d("유저 정보", "${response.body()}") - if (response.isSuccessful) { - response.body()?.let { - adapter.setRepoList(it.data) - } - } - } - override fun onFailure(call: Call, t: Throwable) { - Snackbar.make(binding.root, "서버통신 실패", Snackbar.LENGTH_LONG).show() - } + viewModel.fatchUser() + + viewModel.userInfo.observe(viewLifecycleOwner, Observer { + adapter.setRepoList(it) }) } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/MainActivity.kt b/app/src/main/java/org/sopt/sample/MainActivity.kt index 08c8824..ea2455b 100644 --- a/app/src/main/java/org/sopt/sample/MainActivity.kt +++ b/app/src/main/java/org/sopt/sample/MainActivity.kt @@ -6,6 +6,7 @@ import androidx.fragment.app.FragmentContainerView import com.google.android.material.bottomnavigation.BottomNavigationView import org.sopt.sample.base.BindingActivity import org.sopt.sample.databinding.ActivityMainBinding +import timber.log.Timber class MainActivity :BindingActivity(R.layout.activity_main){ private val frame: FragmentContainerView by lazy { // activity_main의 화면 부분 diff --git a/app/src/main/java/org/sopt/sample/SignInActivity.kt b/app/src/main/java/org/sopt/sample/SignInActivity.kt deleted file mode 100644 index e59aea1..0000000 --- a/app/src/main/java/org/sopt/sample/SignInActivity.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.sopt.sample - -import android.content.Intent -import android.os.Bundle -import androidx.activity.viewModels -import org.sopt.sample.base.BindingActivity -import org.sopt.sample.databinding.ActivitySignInBinding -import org.sopt.sample.login.LoginViewModel - -class SignInActivity: BindingActivity(R.layout.activity_sign_in) { - private val viewModel: LoginViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding.viewModel = viewModel - binding.lifecycleOwner = this - // 버튼 클릭 이벤트 - binding.loginBtn.setOnClickListener { - viewModel.login( - binding.editTextId.text.toString(), - binding.editTextPw.text.toString() - ) - } - - viewModel.loginResult.observe(this) { - startActivity(Intent(this, MainActivity::class.java)) - } - binding.registerBtn.setOnClickListener(){ - startActivity(Intent(this,SignUpActivity::class.java)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/SignUpActivity.kt b/app/src/main/java/org/sopt/sample/SignUpActivity.kt index 877ecdd..68597df 100644 --- a/app/src/main/java/org/sopt/sample/SignUpActivity.kt +++ b/app/src/main/java/org/sopt/sample/SignUpActivity.kt @@ -2,104 +2,40 @@ package org.sopt.sample import android.content.Intent import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.View -import androidx.activity.viewModels -import androidx.core.content.ContextCompat +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider import org.sopt.sample.base.BindingActivity import org.sopt.sample.databinding.ActivitySignUpBinding +import org.sopt.sample.presentation.SignInActivity +import org.sopt.sample.signup.SignupRepository import org.sopt.sample.signup.SignupViewModel -import java.util.regex.Pattern +import org.sopt.sample.signup.SignupViewModelFactory class SignUpActivity : BindingActivity(R.layout.activity_sign_up) { - private val viewModel by viewModels() + private lateinit var viewModel: SignupViewModel + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + viewModel = ViewModelProvider(this, SignupViewModelFactory(SignupRepository())) + .get(SignupViewModel::class.java) binding.viewModel = viewModel binding.lifecycleOwner = this - // 버튼 클릭 이벤트 - var validEmail = false - var validPw = false - val EmailPattern = "^(?=.*[A-Za-z])(?=.*[0-9])[A-Za-z[0-9]]{6,10}$" - val pattern1 = Pattern.compile(EmailPattern) - val PwPattern = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&.])[A-Za-z[0-9]$@$!%*#?&.]{6,12}$" - val pattern2 = Pattern.compile(PwPattern) - - - - binding.editTextEmail.addTextChangedListener(object : TextWatcher { - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - // 입력난에 변화가 있을 시 조치 - viewModel.inputEmail.value = binding.editTextEmail.text.toString() - } - override fun afterTextChanged(arg0: Editable) { - // 입력이 끝났을 때 조치 - } - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - // 입력하기 전에 조치 - } - }) - - binding.editTextPw.addTextChangedListener(object : TextWatcher { - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - // 입력난에 변화가 있을 시 조치 - viewModel.inputPw.value = binding.editTextPw.text.toString() - } - override fun afterTextChanged(arg0: Editable) { - // 입력이 끝났을 때 조치 - } - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - // 입력하기 전에 조치 - } - }) - - viewModel.inputEmail.observe(this) { - val matcher1 = pattern1.matcher(it) - if(matcher1.find() == true) { - validEmail = true - binding.editTextEmail.backgroundTintList = - ContextCompat.getColorStateList(applicationContext, R.color.edit_gray) - } - if(validEmail == false) { - binding.tvEmailerror.visibility = View.VISIBLE - binding.editTextEmail.backgroundTintList = ContextCompat.getColorStateList(applicationContext, R.color.red) - } - if(validEmail == true) - binding.tvEmailerror.visibility = View.INVISIBLE - if(validEmail == true && validPw == true) - binding.finishBtn.isEnabled = true - } - - viewModel.inputPw.observe(this) { - val matcher2 = pattern2.matcher(it) - if(matcher2.find() == true) { - validPw = true - binding.editTextPw.backgroundTintList = - ContextCompat.getColorStateList(applicationContext, R.color.edit_gray) - } - if(validPw == false) { - binding.tvPwerror.visibility = View.VISIBLE - binding.editTextPw.backgroundTintList = ContextCompat.getColorStateList(applicationContext, R.color.red) - } - if(validPw == true) - binding.tvPwerror.visibility = View.INVISIBLE - if(validEmail == true && validPw == true) - binding.finishBtn.isEnabled = true - } + addListeners() + addObservers() + } + private fun addListeners(){ binding.finishBtn.setOnClickListener { - viewModel.signup( - binding.editTextEmail.text.toString(), - binding.editTextPw.text.toString(), - binding.editTextName.text.toString() - ) + viewModel.signup() } + } + private fun addObservers() { viewModel.signupResult.observe(this) { - startActivity(Intent(this,SignInActivity::class.java)) + startActivity(Intent(this, SignInActivity::class.java)) + Toast.makeText(this,getString(R.string.sign_up_success_toast_msg), Toast.LENGTH_LONG).show() } } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/adapter/HeaderViewHolder.kt b/app/src/main/java/org/sopt/sample/adapter/HeaderViewHolder.kt index 60d8dea..c0be7dd 100644 --- a/app/src/main/java/org/sopt/sample/adapter/HeaderViewHolder.kt +++ b/app/src/main/java/org/sopt/sample/adapter/HeaderViewHolder.kt @@ -1,11 +1,10 @@ package org.sopt.sample.adapter import androidx.recyclerview.widget.RecyclerView -import org.sopt.sample.data.Repo -import org.sopt.sample.databinding.LayoutHeaderBinding +import org.sopt.sample.databinding.ItemHeaderBinding class TitleViewHolder ( - private val binding: LayoutHeaderBinding + private val binding: ItemHeaderBinding ): RecyclerView.ViewHolder(binding.root){ } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/adapter/RepoViewHolder.kt b/app/src/main/java/org/sopt/sample/adapter/RepoViewHolder.kt index 6ee5dfa..dcb0701 100644 --- a/app/src/main/java/org/sopt/sample/adapter/RepoViewHolder.kt +++ b/app/src/main/java/org/sopt/sample/adapter/RepoViewHolder.kt @@ -2,16 +2,14 @@ package org.sopt.sample.adapter import androidx.recyclerview.widget.RecyclerView import coil.api.load -import org.sopt.sample.databinding.LayoutGithubRepoBinding +import org.sopt.sample.data.UserData +import org.sopt.sample.databinding.ItemUserInfoBinding import org.sopt.sample.remote.ResponseUserDTO class RepoViewHolder( - private val binding: LayoutGithubRepoBinding + private val binding: ItemUserInfoBinding ): RecyclerView.ViewHolder(binding.root){ fun onBind(data: ResponseUserDTO.Data){ - binding.imgGithub.load(data.avatar) - binding.txtGithubName.setText(data.first_name) - binding.txtGithubAuthor.setText(data.email) + binding.data = data } - } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/adapter/rvAdapter.kt b/app/src/main/java/org/sopt/sample/adapter/rvAdapter.kt index 52af933..5d30ed4 100644 --- a/app/src/main/java/org/sopt/sample/adapter/rvAdapter.kt +++ b/app/src/main/java/org/sopt/sample/adapter/rvAdapter.kt @@ -3,8 +3,9 @@ import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import org.sopt.sample.databinding.LayoutGithubRepoBinding -import org.sopt.sample.databinding.LayoutHeaderBinding +import org.sopt.sample.data.UserData +import org.sopt.sample.databinding.ItemHeaderBinding +import org.sopt.sample.databinding.ItemUserInfoBinding import org.sopt.sample.remote.ResponseUserDTO class rvAdapter(context : Context):RecyclerView.Adapter() { @@ -15,11 +16,11 @@ class rvAdapter(context : Context):RecyclerView.Adapter override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when(viewType){ REPO_TYPE ->{ - val binding = LayoutGithubRepoBinding.inflate(inflater, parent,false) + val binding = ItemUserInfoBinding.inflate(inflater, parent,false) RepoViewHolder(binding) } HEADER_TYPE ->{ - val binding = LayoutHeaderBinding.inflate(inflater, parent,false) + val binding = ItemHeaderBinding.inflate(inflater, parent,false) TitleViewHolder(binding) } else -> throw RuntimeException("Error") diff --git a/app/src/main/java/org/sopt/sample/data/Repo.kt b/app/src/main/java/org/sopt/sample/data/UserData.kt similarity index 85% rename from app/src/main/java/org/sopt/sample/data/Repo.kt rename to app/src/main/java/org/sopt/sample/data/UserData.kt index 96378cf..de82a51 100644 --- a/app/src/main/java/org/sopt/sample/data/Repo.kt +++ b/app/src/main/java/org/sopt/sample/data/UserData.kt @@ -2,10 +2,10 @@ package org.sopt.sample.data import androidx.annotation.DrawableRes -data class Repo ( +data class UserData ( @DrawableRes val image : Int, val name: String, - val author: String, + val email: String, val viewType: Int) { companion object { diff --git a/app/src/main/java/org/sopt/sample/data/datasource/LoginDataSource.kt b/app/src/main/java/org/sopt/sample/data/datasource/LoginDataSource.kt new file mode 100644 index 0000000..b05d0b7 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/data/datasource/LoginDataSource.kt @@ -0,0 +1,10 @@ +package org.sopt.sample.data.datasource + +import org.sopt.sample.remote.RequestLoginDTO +import org.sopt.sample.remote.ResponseLoginDTO +import org.sopt.sample.remote.ServicePool + +class LoginDataSource(private val service: ServicePool) { + suspend fun login(requestLoginDTO: RequestLoginDTO): ResponseLoginDTO = + service.loginService.login(requestLoginDTO) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/data/repositoryImpl/LoginRepositoryImpl.kt b/app/src/main/java/org/sopt/sample/data/repositoryImpl/LoginRepositoryImpl.kt new file mode 100644 index 0000000..30d4436 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/data/repositoryImpl/LoginRepositoryImpl.kt @@ -0,0 +1,21 @@ +package org.sopt.sample.data.repositoryImpl + +import org.sopt.sample.data.datasource.LoginDataSource +import org.sopt.sample.domain.LoginRepository +import org.sopt.sample.remote.RequestLoginDTO +import timber.log.Timber + +class LoginRepositoryImpl( + private val loginDataSource: LoginDataSource +): LoginRepository { + override suspend fun login(email: String, password: String) : Boolean = + kotlin.runCatching { + loginDataSource.login(RequestLoginDTO(email, password)) + }.fold({ + Timber.d("로그인 서버통신 성공") + true + }, { + Timber.d("로그인 서버통신 실패") + false + }) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/domain/LoginRepository.kt b/app/src/main/java/org/sopt/sample/domain/LoginRepository.kt new file mode 100644 index 0000000..452c87c --- /dev/null +++ b/app/src/main/java/org/sopt/sample/domain/LoginRepository.kt @@ -0,0 +1,5 @@ +package org.sopt.sample.domain + +interface LoginRepository { + suspend fun login(email: String, password: String) : Boolean +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/login/LoginViewModel.kt b/app/src/main/java/org/sopt/sample/login/LoginViewModel.kt deleted file mode 100644 index 5346aa0..0000000 --- a/app/src/main/java/org/sopt/sample/login/LoginViewModel.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.sopt.sample.login - -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.sopt.sample.remote.* -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class LoginViewModel: ViewModel() { - private val _loginResult: MutableLiveData = MutableLiveData() - val loginResult: LiveData - get() = _loginResult - private val loginService = ServicePool.loginService - fun login(email: String, password: String) { - loginService.login( - RequestLoginDTO(email, password) - ).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - Log.d("로그인 성공", "${response.body()}") - _loginResult.value = response.body() - } - - override fun onFailure(call: Call, t: Throwable) { - TODO("Not yet implemented") - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/presentation/LoginViewModel.kt b/app/src/main/java/org/sopt/sample/presentation/LoginViewModel.kt new file mode 100644 index 0000000..395d6ef --- /dev/null +++ b/app/src/main/java/org/sopt/sample/presentation/LoginViewModel.kt @@ -0,0 +1,24 @@ +package org.sopt.sample.presentation + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import org.sopt.sample.data.repositoryImpl.LoginRepositoryImpl + +class LoginViewModel (private val repository: LoginRepositoryImpl): ViewModel() { + private val _loginResult: MutableLiveData = MutableLiveData() + val loginResult: LiveData + get() = _loginResult + val inputEmail = MutableLiveData() + val inputPw = MutableLiveData() + + fun login() { + viewModelScope.launch { + val isSuccesful = repository.login(inputEmail.value.toString(), inputPw.value.toString()) + if(isSuccesful) + _loginResult.value = isSuccesful + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/presentation/LoginViewModelFactory.kt b/app/src/main/java/org/sopt/sample/presentation/LoginViewModelFactory.kt new file mode 100644 index 0000000..84bf978 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/presentation/LoginViewModelFactory.kt @@ -0,0 +1,15 @@ +package org.sopt.sample.presentation + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import org.sopt.sample.data.repositoryImpl.LoginRepositoryImpl + +class LoginViewModelFactory(private val loginRepository: LoginRepositoryImpl):ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { + LoginViewModel(loginRepository) as T + } else { + throw IllegalArgumentException() + } + } +} diff --git a/app/src/main/java/org/sopt/sample/presentation/SignInActivity.kt b/app/src/main/java/org/sopt/sample/presentation/SignInActivity.kt new file mode 100644 index 0000000..9449cb4 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/presentation/SignInActivity.kt @@ -0,0 +1,52 @@ +package org.sopt.sample.presentation + +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import org.sopt.sample.MainActivity +import org.sopt.sample.R +import org.sopt.sample.SignUpActivity +import org.sopt.sample.base.BindingActivity +import org.sopt.sample.data.datasource.LoginDataSource +import org.sopt.sample.data.repositoryImpl.LoginRepositoryImpl +import org.sopt.sample.databinding.ActivitySignInBinding +import org.sopt.sample.remote.ServicePool + +class SignInActivity: BindingActivity(R.layout.activity_sign_in) { + private lateinit var viewModel: LoginViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProvider(this, LoginViewModelFactory(LoginRepositoryImpl( + LoginDataSource(ServicePool) + ))) + .get(LoginViewModel::class.java) + + binding.viewModel = viewModel + binding.lifecycleOwner = this + + addListeners() + addObservers() + } + private fun addListeners(){ + binding.loginBtn.setOnClickListener { + viewModel.login() + } + binding.registerBtn.setOnClickListener(){ + startActivity(Intent(this, SignUpActivity::class.java)) + } + } + + private fun addObservers() { + viewModel.loginResult.observe(this) { + Toast.makeText(this, getString(R.string.sign_in_success_toast_msg), Toast.LENGTH_LONG).show() + moveToMain() + } + } + + private fun moveToMain() { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/remote/LoginService.kt b/app/src/main/java/org/sopt/sample/remote/LoginService.kt index 491902b..fa8af0b 100644 --- a/app/src/main/java/org/sopt/sample/remote/LoginService.kt +++ b/app/src/main/java/org/sopt/sample/remote/LoginService.kt @@ -1,12 +1,11 @@ package org.sopt.sample.remote -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST interface LoginService { @POST("api/user/signin") - fun login( + suspend fun login( @Body requestLoginDTO: RequestLoginDTO - ): Call + ): ResponseLoginDTO } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/remote/ResponseUserDTO.kt b/app/src/main/java/org/sopt/sample/remote/ResponseUserDTO.kt index 43a835e..08f4f46 100644 --- a/app/src/main/java/org/sopt/sample/remote/ResponseUserDTO.kt +++ b/app/src/main/java/org/sopt/sample/remote/ResponseUserDTO.kt @@ -1,5 +1,6 @@ package org.sopt.sample.remote +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -7,7 +8,8 @@ data class ResponseUserDTO( val page: Int, val per_page: Int, val total: Int, - val total_pages: Int, + @SerialName("total_pages") + val totalPages: Int, val data: List, val support: Support, @@ -16,8 +18,10 @@ data class ResponseUserDTO( data class Data( val id: Int, val email: String, - val first_name: String, - val last_name: String, + @SerialName("first_name") + val firstName: String, + @SerialName("last_name") + val lastName: String, val avatar: String, ) diff --git a/app/src/main/java/org/sopt/sample/remote/SignupService.kt b/app/src/main/java/org/sopt/sample/remote/SignupService.kt index 6cb0f0e..5b9a32d 100644 --- a/app/src/main/java/org/sopt/sample/remote/SignupService.kt +++ b/app/src/main/java/org/sopt/sample/remote/SignupService.kt @@ -1,12 +1,13 @@ package org.sopt.sample.remote import retrofit2.Call +import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST interface SignupService { @POST("api/user/signup") - fun signup( + suspend fun signup( @Body requestSignupDTO: RequestSignupDTO - ): Call + ): Response } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/remote/UserService.kt b/app/src/main/java/org/sopt/sample/remote/UserService.kt index b6af624..be8e7f8 100644 --- a/app/src/main/java/org/sopt/sample/remote/UserService.kt +++ b/app/src/main/java/org/sopt/sample/remote/UserService.kt @@ -1,9 +1,10 @@ package org.sopt.sample.remote import retrofit2.Call +import retrofit2.Response import retrofit2.http.GET interface UserService { @GET("api/users?page=2") - fun user(): Call + suspend fun user(): Response } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/signup/SignupRepository.kt b/app/src/main/java/org/sopt/sample/signup/SignupRepository.kt new file mode 100644 index 0000000..9cbead5 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/signup/SignupRepository.kt @@ -0,0 +1,18 @@ +package org.sopt.sample.signup + +import org.sopt.sample.remote.RequestSignupDTO +import org.sopt.sample.remote.ServicePool +import timber.log.Timber + +class SignupRepository { + suspend fun signup(email: String, password: String, name: String) : Boolean= + kotlin.runCatching { + ServicePool.signupService.signup(RequestSignupDTO(email, password, name)) + }.fold({ + Timber.d("회원가입 서버통신 성공") + true + }, { + Timber.d("회원가입 서버통신 실패") + false + }) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/signup/SignupViewModel.kt b/app/src/main/java/org/sopt/sample/signup/SignupViewModel.kt index a996d15..b0ced28 100644 --- a/app/src/main/java/org/sopt/sample/signup/SignupViewModel.kt +++ b/app/src/main/java/org/sopt/sample/signup/SignupViewModel.kt @@ -1,59 +1,39 @@ package org.sopt.sample.signup -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations -import org.sopt.sample.remote.RequestSignupDTO -import org.sopt.sample.remote.ResponseSignupDTO -import org.sopt.sample.remote.ServicePool -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response -import java.util.regex.Pattern - -class SignupViewModel: ViewModel() { - private val _signupResult: MutableLiveData = MutableLiveData() - val signupResult: LiveData +import androidx.lifecycle.* +import kotlinx.coroutines.launch + +class SignupViewModel(private val repository: SignupRepository) : ViewModel() { + private val _signupResult: MutableLiveData = MutableLiveData() + val signupResult: LiveData get() = _signupResult val inputEmail = MutableLiveData() val inputPw = MutableLiveData() - val isvalidEmail : LiveData = Transformations.map(inputEmail){emailCheck(it)} - val isvalidPw : LiveData = Transformations.map(inputPw){pwCheck(it)} - - private val signupService = ServicePool.signupService - fun signup(email: String, password: String, name: String){ - signupService.signup( - RequestSignupDTO(email, password, name) - ).enqueue(object : Callback{ - override fun onResponse( - call: Call, - response: Response - ) { - _signupResult.value = response.body() - Log.d("회원가입 성공", "${response.body()}") - } - - override fun onFailure(call: Call, t: Throwable) { - TODO("Not yet implemented") - } - }) - - } - private fun emailCheck(email: String):Boolean{ - val emailPattern = "^(?=.*[A-Za-z])(?=.*[0-9])[A-Za-z[0-9]]{6,10}$" - val pattern = Pattern.compile(emailPattern) - val matcher = pattern.matcher(email) - return matcher.matches() + val inputName = MutableLiveData() + + val isvalidEmail: LiveData + get() = Transformations.map(inputEmail) + { email -> email?.matches(EMAIL_PATTERN.toRegex()) } + val isvalidPw: LiveData + get() = Transformations.map(inputPw) { pw -> pw?.matches(PASSWORD_PATTERN.toRegex()) } + + fun signup() { + viewModelScope.launch { + val isSuccesful = repository.signup( + inputEmail.value.toString(), inputPw.value.toString(), + inputName.value.toString() + ) + if (isSuccesful) + _signupResult.value = isSuccesful + } } - private fun pwCheck(pw: String):Boolean{ - val pwPattern = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&.])[A-Za-z[0-9]$@$!%*#?&.]{6,12}$" - val pattern = Pattern.compile(pwPattern) - val matcher = pattern.matcher(pw) - return matcher.matches() + companion object { + private const val EMAIL_PATTERN = "^(?=.*[A-Za-z])(?=.*[0-9])[A-Za-z[0-9]]{6,10}\$" + private const val PASSWORD_PATTERN = + "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[\$@\$!%*#?&.])[A-Za-z[0-9]\$@\$!%*#?&.]{6,12}\$" } + } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/signup/SignupViewModelFactory.kt b/app/src/main/java/org/sopt/sample/signup/SignupViewModelFactory.kt new file mode 100644 index 0000000..0550bba --- /dev/null +++ b/app/src/main/java/org/sopt/sample/signup/SignupViewModelFactory.kt @@ -0,0 +1,14 @@ +package org.sopt.sample.signup + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class SignupViewModelFactory (private val signupRepository: SignupRepository): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return if (modelClass.isAssignableFrom(SignupViewModel::class.java)) { + SignupViewModel(signupRepository) as T + } else { + throw IllegalArgumentException() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/user/UserRepository.kt b/app/src/main/java/org/sopt/sample/user/UserRepository.kt new file mode 100644 index 0000000..299681d --- /dev/null +++ b/app/src/main/java/org/sopt/sample/user/UserRepository.kt @@ -0,0 +1,10 @@ +package org.sopt.sample.user + +import org.sopt.sample.remote.ResponseUserDTO +import org.sopt.sample.remote.UserServicePool +import retrofit2.Response + +class UserRepository { + suspend fun user(): Response = + UserServicePool.userService.user() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/sample/user/UserViewModel.kt b/app/src/main/java/org/sopt/sample/user/UserViewModel.kt new file mode 100644 index 0000000..a4d5d85 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/user/UserViewModel.kt @@ -0,0 +1,23 @@ +package org.sopt.sample.user + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import org.sopt.sample.remote.ResponseUserDTO + +class UserViewModel(private val repository: UserRepository) : ViewModel() { + private val _userInfo = MutableLiveData>() + val userInfo: LiveData> + get() = _userInfo + + private var items : List = emptyList() + + fun fatchUser() { + viewModelScope.launch { + items = repository.user().body()!!.data + _userInfo.value = items + } + } +} diff --git a/app/src/main/java/org/sopt/sample/user/UserViewModelFactory.kt b/app/src/main/java/org/sopt/sample/user/UserViewModelFactory.kt new file mode 100644 index 0000000..1aacb59 --- /dev/null +++ b/app/src/main/java/org/sopt/sample/user/UserViewModelFactory.kt @@ -0,0 +1,14 @@ +package org.sopt.sample.user + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class UserViewModelFactory (private val userRepository: UserRepository): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return if (modelClass.isAssignableFrom(UserViewModel::class.java)) { + UserViewModel(userRepository) as T + } else { + throw IllegalArgumentException() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 043d840..a460859 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -35,6 +35,5 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txt_name" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sign_in.xml b/app/src/main/res/layout/activity_sign_in.xml index 770e6c6..443832c 100644 --- a/app/src/main/res/layout/activity_sign_in.xml +++ b/app/src/main/res/layout/activity_sign_in.xml @@ -8,13 +8,13 @@ + type="org.sopt.sample.presentation.LoginViewModel" /> + tools:context=".presentation.SignInActivity"> @@ -46,7 +46,8 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="40dp" android:layout_marginTop="20dp" - android:hint="아이디를 입력하세요" + android:hint="@string/sign_id_hint" + android:text="@={viewModel.inputEmail}" app:layout_constraintTop_toBottomOf="@+id/txt_id_title" tools:layout_editor_absoluteX="40dp" /> @@ -56,7 +57,7 @@ android:layout_height="wrap_content" android:layout_marginStart="40dp" android:layout_marginTop="32dp" - android:text="비밀번호" + android:text="@string/sign_pw_label" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/editText_id" /> @@ -67,8 +68,9 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="40dp" android:layout_marginTop="20dp" - android:hint="비밀번호를 입력하세요" + android:hint="@string/sign_pw_hint" android:inputType="textPassword" + android:text="@={viewModel.inputPw}" app:layout_constraintTop_toBottomOf="@+id/txt_password_title" />