diff --git a/app/build.gradle b/app/build.gradle index 3a2689d..62cb488 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,12 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' } +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + android { namespace 'com.sopt.now' compileSdk 34 @@ -15,6 +19,8 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + buildConfigField "String", "AUTH_BASE_URL", properties["base.url"] + buildConfigField "String", "FOLLOWER_URL", properties["follower.url"] } buildTypes { @@ -32,12 +38,13 @@ android { } buildFeatures { viewBinding true + buildConfig true } } dependencies { - implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.core:core-ktx:1.13.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' @@ -45,8 +52,24 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + // 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.8.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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec5264f..507b979 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + diff --git a/app/src/main/java/com/sopt/now/data/ItemData.kt b/app/src/main/java/com/sopt/now/data/model/ItemData.kt similarity index 81% rename from app/src/main/java/com/sopt/now/data/ItemData.kt rename to app/src/main/java/com/sopt/now/data/model/ItemData.kt index 3e24bbb..5eadebf 100644 --- a/app/src/main/java/com/sopt/now/data/ItemData.kt +++ b/app/src/main/java/com/sopt/now/data/model/ItemData.kt @@ -1,4 +1,4 @@ -package com.sopt.now.data +package com.sopt.now.data.model import androidx.annotation.DrawableRes @@ -12,7 +12,7 @@ sealed class ItemData { data class Friend( - @DrawableRes val profileImage: Int?, + val profileImage: String?, val name: String, val description: String?, ) : ItemData() diff --git a/app/src/main/java/com/sopt/now/data/model/RequestSignInDto.kt b/app/src/main/java/com/sopt/now/data/model/RequestSignInDto.kt new file mode 100644 index 0000000..c6ba27e --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/RequestSignInDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestSignInDto( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("password") + val password: String, +) diff --git a/app/src/main/java/com/sopt/now/data/model/RequestSignUpDto.kt b/app/src/main/java/com/sopt/now/data/model/RequestSignUpDto.kt new file mode 100644 index 0000000..a8145ab --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/RequestSignUpDto.kt @@ -0,0 +1,16 @@ +package com.sopt.now.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/data/model/ResponseInfoDto.kt b/app/src/main/java/com/sopt/now/data/model/ResponseInfoDto.kt new file mode 100644 index 0000000..a712ac8 --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/ResponseInfoDto.kt @@ -0,0 +1,24 @@ +package com.sopt.now.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/data/model/ResponseSignInDto.kt b/app/src/main/java/com/sopt/now/data/model/ResponseSignInDto.kt new file mode 100644 index 0000000..e1ef401 --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/ResponseSignInDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseSignInDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) diff --git a/app/src/main/java/com/sopt/now/data/model/ResponseSignUpDto.kt b/app/src/main/java/com/sopt/now/data/model/ResponseSignUpDto.kt new file mode 100644 index 0000000..3dc30ef --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/ResponseSignUpDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseSignUpDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) diff --git a/app/src/main/java/com/sopt/now/data/model/ResponseUserDto.kt b/app/src/main/java/com/sopt/now/data/model/ResponseUserDto.kt new file mode 100644 index 0000000..aca3029 --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/ResponseUserDto.kt @@ -0,0 +1,29 @@ +package com.sopt.now.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserDataResponse( + 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, +) diff --git a/app/src/main/java/com/sopt/now/data/model/SignInState.kt b/app/src/main/java/com/sopt/now/data/model/SignInState.kt new file mode 100644 index 0000000..8756c3b --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/SignInState.kt @@ -0,0 +1,6 @@ +package com.sopt.now.data.model + +data class SignInState( + val isSuccess: Boolean, + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/data/model/SignUpState.kt b/app/src/main/java/com/sopt/now/data/model/SignUpState.kt new file mode 100644 index 0000000..c7c43d4 --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/model/SignUpState.kt @@ -0,0 +1,6 @@ +package com.sopt.now.data.model + +data class SignUpState( + val isSuccess: Boolean, + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/data/module/ApiFactory.kt b/app/src/main/java/com/sopt/now/data/module/ApiFactory.kt new file mode 100644 index 0000000..9c7ae56 --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/module/ApiFactory.kt @@ -0,0 +1,42 @@ +package com.sopt.now.data.module + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.sopt.now.compose.BuildConfig +import com.sopt.now.data.network.AuthService +import com.sopt.now.data.network.FollowerService +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.create + +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 createBaseRetrofit(): T = baseRetrofit.create() + inline fun createFollowerRetrofit(): T = followerRetrofit.create() +} + +object ServicePool { + val authService = ApiFactory.createBaseRetrofit() + val followerService = ApiFactory.createFollowerRetrofit() +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/data/module/AuthInterceptor.kt b/app/src/main/java/com/sopt/now/data/module/AuthInterceptor.kt new file mode 100644 index 0000000..ac96a76 --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/module/AuthInterceptor.kt @@ -0,0 +1,17 @@ +package com.sopt.now.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/data/network/AuthService.kt b/app/src/main/java/com/sopt/now/data/network/AuthService.kt new file mode 100644 index 0000000..6d2995e --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/network/AuthService.kt @@ -0,0 +1,27 @@ +package com.sopt.now.data.network + +import com.sopt.now.data.model.RequestSignInDto +import com.sopt.now.data.model.RequestSignUpDto +import com.sopt.now.data.model.ResponseInfoDto +import com.sopt.now.data.model.ResponseSignInDto +import com.sopt.now.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/data/network/FollowerService.kt b/app/src/main/java/com/sopt/now/data/network/FollowerService.kt new file mode 100644 index 0000000..33100bb --- /dev/null +++ b/app/src/main/java/com/sopt/now/data/network/FollowerService.kt @@ -0,0 +1,13 @@ +package com.sopt.now.data.network + +import com.sopt.now.data.model.UserDataResponse +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/ui/adapter/ItemAdapter.kt b/app/src/main/java/com/sopt/now/ui/adapter/ItemAdapter.kt index b1dc9a6..3886cda 100644 --- a/app/src/main/java/com/sopt/now/ui/adapter/ItemAdapter.kt +++ b/app/src/main/java/com/sopt/now/ui/adapter/ItemAdapter.kt @@ -5,7 +5,8 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.sopt.now.data.ItemData +import com.bumptech.glide.Glide +import com.sopt.now.data.model.ItemData import com.sopt.now.databinding.ItemFriendBinding import com.sopt.now.databinding.ItemMyProfileBinding @@ -47,9 +48,12 @@ class ItemAdapter(private val items: MutableList) : RecyclerView.Adapt } is ItemData.Friend -> { - (holder as FriendViewHolder).profileImage.setImageResource(item.profileImage!!) - holder.name.text = item.name + (holder as FriendViewHolder).name.text = item.name holder.description.text = item.description + Glide.with(holder.profileImage.context) + .load(item.profileImage) + .into(holder.profileImage) + holder.itemView.setOnClickListener { itemClick?.onItemClick(it, position) } diff --git a/app/src/main/java/com/sopt/now/ui/home/HomFragment.kt b/app/src/main/java/com/sopt/now/ui/home/HomeFragment.kt similarity index 64% rename from app/src/main/java/com/sopt/now/ui/home/HomFragment.kt rename to app/src/main/java/com/sopt/now/ui/home/HomeFragment.kt index 9318151..bcdaa64 100644 --- a/app/src/main/java/com/sopt/now/ui/home/HomFragment.kt +++ b/app/src/main/java/com/sopt/now/ui/home/HomeFragment.kt @@ -6,10 +6,14 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import com.sopt.now.data.ItemData +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.LinearLayoutManager import com.sopt.now.databinding.FragmentHomeBinding import com.sopt.now.ui.adapter.ItemAdapter import com.sopt.now.ui.home.viewModel.HomeViewModel +import kotlinx.coroutines.launch class HomeFragment : Fragment() { private var _binding: FragmentHomeBinding? = null @@ -31,10 +35,8 @@ class HomeFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val items = viewModel.friendList - val friendAdapter = ItemAdapter(items) - binding.rvFriends.adapter = friendAdapter - setFriendList(items) + + setCollect() } override fun onDestroy() { @@ -42,8 +44,20 @@ class HomeFragment : Fragment() { super.onDestroy() } - private fun setFriendList(friendList: List) { - friendList.toList() + private fun setCollect() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.followerState.collect { + setAdapter() + } + } + } } + private fun setAdapter() { + val friendAdapter = ItemAdapter(viewModel.friendList) + + binding.rvFriends.layoutManager = LinearLayoutManager(requireContext()) + binding.rvFriends.adapter = friendAdapter + } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/home/viewModel/HomeViewModel.kt b/app/src/main/java/com/sopt/now/ui/home/viewModel/HomeViewModel.kt index f390781..0c37aa6 100644 --- a/app/src/main/java/com/sopt/now/ui/home/viewModel/HomeViewModel.kt +++ b/app/src/main/java/com/sopt/now/ui/home/viewModel/HomeViewModel.kt @@ -2,75 +2,61 @@ package com.sopt.now.ui.home.viewModel import androidx.lifecycle.ViewModel import com.sopt.now.R -import com.sopt.now.data.ItemData +import com.sopt.now.data.model.ItemData +import com.sopt.now.data.model.UserData +import com.sopt.now.data.model.UserDataResponse +import com.sopt.now.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() { - val friendList = mutableListOf( + private val followerService by lazy { ServicePool.followerService } + private val _followerState = MutableStateFlow>(emptyList()) + val followerState = _followerState.asStateFlow() + + val friendList = mutableListOf( ItemData.MyProfile( profileImage = R.drawable.img_arin, name = "김아린", description = "업보 청산 중..", ), - ItemData.Friend( - profileImage = R.drawable.img_profile0, - name = "이의경", - description = "김아린 과제 벼락치기 하지 마라", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile1, - name = "최준서", - description = "오운완 ㅋㅋ", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile2, - name = "이연진", - description = "아리니 넘 기엽당..", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile3, - name = "손민재", - description = "점심 뭐 먹지?", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile4, - name = "홍해인", - description = "난 눈물의 여왕이야", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile5, - name = "백현우", - description = "눈물의여왕시작하지말걸공부가안된다", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile6, - name = "이서경", - description = "저는 환연 과몰입러예요", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile7, - name = "이주원", - description = "너가 자기야 미안해 했잖아?", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile8, - name = "김광태", - description = "내일 뭐 해?", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile9, - name = "정현규", - description = "내봬누", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile10, - name = "성해은", - description = "벌써 스물 아홉이야", - ), - ItemData.Friend( - profileImage = R.drawable.img_profile11, - name = "정규민", - description = "오마카세 사줄게", - ), ) + + 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) { + } + }) + } + + fun mapFollowersToFriendList(followers: List) { + friendList.addAll(followers.map { follower -> + ItemData.Friend( + profileImage = follower.avatar, + name = "${follower.firstName} ${follower.lastName}", + description = follower.email + ) + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/login/LoginActivity.kt b/app/src/main/java/com/sopt/now/ui/login/LoginActivity.kt deleted file mode 100644 index 15624df..0000000 --- a/app/src/main/java/com/sopt/now/ui/login/LoginActivity.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.sopt.now.ui.login - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.view.MotionEvent -import android.view.inputmethod.InputMethodManager -import android.widget.Toast -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import androidx.appcompat.app.AppCompatActivity -import com.sopt.now.databinding.ActivityLoginBinding -import com.sopt.now.ui.main.MainActivity -import com.sopt.now.ui.signUp.SignUpActivity - -class LoginActivity : AppCompatActivity() { - private lateinit var binding: ActivityLoginBinding - private var isCheckLogin: Boolean = false - private var savedId: String = "" - private var savedPw: String = "" - private var savedNickname: String = "" - private var savedMbti: String = "" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityLoginBinding.inflate(layoutInflater) - setContentView(binding.root) - - setLoginButtonClickListeners() - setSignUpButtonClickListeners() - } - - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - hideKeyboard() - return super.dispatchTouchEvent(ev) - } - - private val startForResult = - registerForActivityResult(StartActivityForResult()) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK) { - savedId = result.data?.getStringExtra("id") ?: "" - savedPw = result.data?.getStringExtra("pw") ?: "" - savedNickname = result.data?.getStringExtra("nickname") ?: "" - savedMbti = result.data?.getStringExtra("mbti") ?: "" - } - } - - private fun setLoginButtonClickListeners() { - binding.btnLogin.setOnClickListener { - val id = binding.edtLoginId.text.toString() - val pw = binding.edtLoginPw.text.toString() - isCheckLogin = checkLogin(id, pw, savedId, savedPw) - if (isCheckLogin) { - showToastMessage("로그인 성공!") - moveToMain() - } else { - showToastMessage("로그인에 실패하였습니다.") - } - } - } - - private fun setSignUpButtonClickListeners() { - binding.btnSignUp.setOnClickListener { - moveToSignUp() - } - } - - - private fun hideKeyboard() { - val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0) - } - - - private fun moveToSignUp() { - val intent = Intent(this, SignUpActivity::class.java) - startForResult.launch(intent) - } - - private fun moveToMain() { - val intent = Intent(this, MainActivity::class.java).apply { - putExtra("id", savedId) - putExtra("pw", savedPw) - putExtra("nickname", savedNickname) - putExtra("mbti", savedMbti) - } - startActivity(intent) - setResult(RESULT_OK, intent) - finish() - } - - private fun showToastMessage(text: String = "") { - Toast.makeText( - this, - text, - Toast.LENGTH_SHORT - ).show() - } - - private fun checkLogin(id: String, pw: String, savedId: String, savedPw: String): Boolean { - return id.isNotEmpty() && pw.isNotEmpty() && id == savedId && pw == savedPw - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/main/MainActivity.kt b/app/src/main/java/com/sopt/now/ui/main/MainActivity.kt index 2156d96..56805e7 100644 --- a/app/src/main/java/com/sopt/now/ui/main/MainActivity.kt +++ b/app/src/main/java/com/sopt/now/ui/main/MainActivity.kt @@ -14,7 +14,7 @@ class MainActivity : AppCompatActivity() { private lateinit var id: String private lateinit var pw: String private lateinit var nickname: String - private lateinit var mbti: String + private lateinit var phoneNumber: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -36,7 +36,7 @@ class MainActivity : AppCompatActivity() { id = getStringExtra(ID).toString() pw = getStringExtra(PW).toString() nickname = getStringExtra(NICKNAME).toString() - mbti = getStringExtra(MBTI).toString() + phoneNumber = getStringExtra(PHONE_NUMBER).toString() } } @@ -66,9 +66,7 @@ class MainActivity : AppCompatActivity() { R.id.menu_my_page -> { replaceFragment( - MyPageFragment( - id, pw, nickname, mbti - ) + MyPageFragment() ) true } @@ -88,6 +86,6 @@ class MainActivity : AppCompatActivity() { const val ID = "id" const val PW = "pw" const val NICKNAME = "nickname" - const val MBTI = "mbti" + const val PHONE_NUMBER = "phoneNumber" } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/myPage/MyPageFragment.kt b/app/src/main/java/com/sopt/now/ui/myPage/MyPageFragment.kt index 80de876..4f16028 100644 --- a/app/src/main/java/com/sopt/now/ui/myPage/MyPageFragment.kt +++ b/app/src/main/java/com/sopt/now/ui/myPage/MyPageFragment.kt @@ -5,20 +5,22 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.sopt.now.databinding.FragmentMyPageBinding +import com.sopt.now.ui.myPage.viewModel.MyPageViewModel +import kotlinx.coroutines.launch -class MyPageFragment( - private val id: String, - private val pw: String, - private val nickname: String, - private val mbti: String, -) : Fragment() { +class MyPageFragment : Fragment() { private var _binding: FragmentMyPageBinding? = null private val binding get() = requireNotNull(_binding) { "바인딩 객체 미생성" } + private val viewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, @@ -32,21 +34,25 @@ class MyPageFragment( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setMainProfile(id, pw, nickname, mbti) - - } - - private fun setMainProfile(id: String, pw: String, nickname: String, mbti: String) { - with(binding) { - tvMyId.text = id - tvMyPw.text = pw - tvMyNickname.text = nickname - tvMyMbti.text = mbti - } + setCollect() } override fun onDestroy() { super.onDestroy() _binding = null } + + private fun setCollect() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.infoState.collect { info -> + with(binding) { + tvMyId.text = info.data.authenticationId + tvMyNickname.text = info.data.nickname + tvMyPhoneNumber.text = info.data.phone + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/myPage/viewModel/MyPageViewModel.kt b/app/src/main/java/com/sopt/now/ui/myPage/viewModel/MyPageViewModel.kt new file mode 100644 index 0000000..9b55f15 --- /dev/null +++ b/app/src/main/java/com/sopt/now/ui/myPage/viewModel/MyPageViewModel.kt @@ -0,0 +1,51 @@ +package com.sopt.now.ui.myPage.viewModel + +import androidx.lifecycle.ViewModel +import com.sopt.now.data.model.ResponseInfoDto +import com.sopt.now.data.model.UserInfo +import com.sopt.now.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) { + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/signIn/SignInActivity.kt b/app/src/main/java/com/sopt/now/ui/signIn/SignInActivity.kt new file mode 100644 index 0000000..7658515 --- /dev/null +++ b/app/src/main/java/com/sopt/now/ui/signIn/SignInActivity.kt @@ -0,0 +1,99 @@ +package com.sopt.now.ui.signIn + +import android.content.Intent +import android.os.Bundle +import android.view.MotionEvent +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.sopt.now.data.model.RequestSignInDto +import com.sopt.now.databinding.ActivityLoginBinding +import com.sopt.now.ui.main.MainActivity +import com.sopt.now.ui.signIn.viewModel.SignInViewModel +import com.sopt.now.ui.signUp.SignUpActivity +import kotlinx.coroutines.launch + +class SignInActivity : AppCompatActivity() { + private val binding by lazy { ActivityLoginBinding.inflate(layoutInflater) } + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + setCollect() + setLoginButtonClickListeners() + setSignUpButtonClickListeners() + } + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + hideKeyboard() + return super.dispatchTouchEvent(ev) + } + + private fun setCollect() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.signInState.collect { signInState -> + if (signInState.message.isNotBlank()) { + showToastMessage(signInState.message) + if (signInState.isSuccess) { + moveToMain() + } + } + } + } + } + } + + private fun getSignInRequestDto(): RequestSignInDto { + val id = binding.edtSignInId.text.toString() + val pw = binding.edtSignInPw.text.toString() + + return RequestSignInDto( + authenticationId = id, + password = pw, + ) + } + + private fun setLoginButtonClickListeners() { + binding.btnLogin.setOnClickListener { + viewModel.signIn(getSignInRequestDto()) + } + } + + private fun setSignUpButtonClickListeners() { + binding.btnSignUp.setOnClickListener { + moveToSignUp() + } + } + + + private fun hideKeyboard() { + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0) + } + + + private fun moveToSignUp() { + val intent = Intent(this, SignUpActivity::class.java) + startActivity(intent) + } + + private fun moveToMain() { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } + + private fun showToastMessage(text: String = "") { + Toast.makeText( + this, + text, + Toast.LENGTH_SHORT + ).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/ui/signIn/viewModel/SignInViewModel.kt b/app/src/main/java/com/sopt/now/ui/signIn/viewModel/SignInViewModel.kt new file mode 100644 index 0000000..7536515 --- /dev/null +++ b/app/src/main/java/com/sopt/now/ui/signIn/viewModel/SignInViewModel.kt @@ -0,0 +1,54 @@ +package com.sopt.now.ui.signIn.viewModel + +import androidx.lifecycle.ViewModel +import com.sopt.now.data.model.RequestSignInDto +import com.sopt.now.data.model.ResponseSignInDto +import com.sopt.now.data.model.SignInState +import com.sopt.now.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/ui/signUp/SignUpActivity.kt b/app/src/main/java/com/sopt/now/ui/signUp/SignUpActivity.kt index 1d780bf..1cdbbd4 100644 --- a/app/src/main/java/com/sopt/now/ui/signUp/SignUpActivity.kt +++ b/app/src/main/java/com/sopt/now/ui/signUp/SignUpActivity.kt @@ -4,26 +4,28 @@ import android.os.Bundle import android.view.MotionEvent import android.view.inputmethod.InputMethodManager import android.widget.Toast +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.sopt.now.R +import com.sopt.now.data.model.RequestSignUpDto import com.sopt.now.databinding.ActivitySignUpBinding +import com.sopt.now.ui.signUp.viewModel.SignUpViewModel +import kotlinx.coroutines.launch class SignUpActivity : AppCompatActivity() { - 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 - const val MBTI_LENGTH = 4 - } - private lateinit var binding: ActivitySignUpBinding + private val binding by lazy { ActivitySignUpBinding.inflate(layoutInflater) } + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivitySignUpBinding.inflate(layoutInflater) setContentView(binding.root) setSignUpButton() + setCollect() } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { @@ -38,16 +40,42 @@ class SignUpActivity : AppCompatActivity() { private fun setSignUpButton() { binding.btnSignUp.setOnClickListener { - if (isInputValid()) { - handleValidInput() - } else { - showToastMessage("회원 정보를 모두 입력해주세요.") + when { + isInputValid() -> viewModel.signUp(getSignUpRequestDto()) + else -> showToastMessage(R.string.toast_sign_up_fail) } } } + private fun setCollect() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.signUpState.collect { signUpState -> + if (signUpState.message.isNotBlank()) { + if (signUpState.isSuccess) { + handleValidInput() + } + } + } + } + } + } + + private fun getSignUpRequestDto(): RequestSignUpDto { + val id = binding.edtSignUpId.text.toString() + val password = binding.edtSignUpPw.text.toString() + val nickname = binding.edtSignUpNickname.text.toString() + val phoneNumber = binding.edtSignUpPhoneNumber.text.toString() + return RequestSignUpDto( + authenticationId = id, + password = password, + nickname = nickname, + phone = phoneNumber + ) + } + private fun isInputValid(): Boolean { - return isIdValid() && isPwValid() && isNicknameValid() && isMbtiValid() + return isIdValid() && isPwValid() && isNicknameValid() && isPhoneNumberValid() } private fun isIdValid(): Boolean { @@ -57,7 +85,7 @@ class SignUpActivity : AppCompatActivity() { private fun isPwValid(): Boolean { val pwText = binding.edtSignUpPw.text.toString() - return pwText.isNotBlank() && pwText.length in MIN_LENGTH_PASSWORD..MAX_LENGTH_PASSWORD + return pwText.isNotBlank() && pwText.length >= MIN_LENGTH_PASSWORD } private fun isNicknameValid(): Boolean { @@ -65,26 +93,23 @@ class SignUpActivity : AppCompatActivity() { return nicknameText.isNotBlank() && !nicknameText.contains(" ") } - private fun isMbtiValid(): Boolean { - val mbtiText = binding.edtSignUpMbti.text.toString() - return mbtiText.isNotBlank() && mbtiText.length == MBTI_LENGTH + private fun isPhoneNumberValid(): Boolean { + val phoneNumberText = binding.edtSignUpPhoneNumber.text.toString() + return phoneNumberText.isNotBlank() } private fun handleValidInput() { - showToastMessage("회원가입이 완료되었습니다.") - - intent.apply { - putExtra("id", binding.edtSignUpId.text.toString()) - putExtra("pw", binding.edtSignUpPw.text.toString()) - putExtra("nickname", binding.edtSignUpNickname.text.toString()) - putExtra("mbti", binding.edtSignUpMbti.text.toString()) - } - - setResult(RESULT_OK, intent) + showToastMessage(R.string.toast_sign_up_success) finish() } - private fun showToastMessage(text: String) { + private fun showToastMessage(text: Int) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show() } + + companion object { + const val MIN_LENGTH_LOGIN = 6 + const val MAX_LENGTH_LOGIN = 10 + const val MIN_LENGTH_PASSWORD = 8 + } } diff --git a/app/src/main/java/com/sopt/now/ui/signUp/viewModel/SignUpViewModel.kt b/app/src/main/java/com/sopt/now/ui/signUp/viewModel/SignUpViewModel.kt new file mode 100644 index 0000000..e80cb59 --- /dev/null +++ b/app/src/main/java/com/sopt/now/ui/signUp/viewModel/SignUpViewModel.kt @@ -0,0 +1,53 @@ +package com.sopt.now.ui.signUp.viewModel + +import androidx.lifecycle.ViewModel +import com.sopt.now.data.model.RequestSignUpDto +import com.sopt.now.data.model.ResponseSignUpDto +import com.sopt.now.data.model.SignUpState +import com.sopt.now.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 = "서버 에러") } + } + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 0ce195b..ffa6096 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -5,7 +5,7 @@ android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.login.LoginActivity"> + tools:context=".ui.signIn.SignInActivity"> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9eadf13..e3d3436 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -64,14 +64,14 @@ android:text="@string/pw_label" /> + android:text="@string/phone_number_label" /> diff --git a/app/src/main/res/layout/activity_sign_up.xml b/app/src/main/res/layout/activity_sign_up.xml index 3eeb1db..30c06d5 100644 --- a/app/src/main/res/layout/activity_sign_up.xml +++ b/app/src/main/res/layout/activity_sign_up.xml @@ -108,17 +108,17 @@ android:textSize="14sp" /> diff --git a/app/src/main/res/layout/fragment_my_page.xml b/app/src/main/res/layout/fragment_my_page.xml index 151463d..5de1d86 100644 --- a/app/src/main/res/layout/fragment_my_page.xml +++ b/app/src/main/res/layout/fragment_my_page.xml @@ -16,14 +16,14 @@ @@ -54,24 +54,14 @@ android:text="@string/id_label" /> - - + android:text="@string/phone_number_label" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 978087c..e270d75 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,8 +14,8 @@ SIGN UP 닉네임 닉네임을 입력해주세요 - MBTI - MBTI를 입력해주세요 + 전화번호 + 전화번호를 입력해주세요 @@ -26,4 +26,7 @@ 이름 소개 + + 모든 정보를 입력해주세요. + 회원가입이 완료되었습니다. \ No newline at end of file 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