diff --git a/app/build.gradle b/app/build.gradle index 36b9bd1..1daac53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,13 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.0' + id 'kotlin-parcelize' } +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + android { namespace 'com.sopt.now.compose' compileSdk 34 @@ -15,6 +20,8 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + buildConfigField "String", "AUTH_BASE_URL", properties["auth.base.url"] + vectorDrawables { useSupportLibrary true } @@ -36,6 +43,7 @@ android { buildFeatures { compose true viewBinding true + buildConfig true } composeOptions { kotlinCompilerExtensionVersion '1.5.1' @@ -69,4 +77,24 @@ dependencies { androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' + + //Gson + implementation "com.google.code.gson:gson:2.8.9" + + // Glide + implementation("com.github.bumptech.glide:compose:1.0.0-beta01") + + // retrofit2 + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0") + + // define a BOM and its version + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) + implementation ("androidx.compose.runtime:runtime-livedata:1.0.5") + + // define any required OkHttp artifacts without version + implementation("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:logging-interceptor") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5dd98b0..50dd254 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - diff --git a/app/src/main/java/com/sopt/now/compose/data/ApiFactory.kt b/app/src/main/java/com/sopt/now/compose/data/ApiFactory.kt new file mode 100644 index 0000000..a58765d --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/ApiFactory.kt @@ -0,0 +1,57 @@ +package com.sopt.now.compose.data + +import android.util.Log +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.sopt.now.BuildConfig +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Response +import retrofit2.Retrofit +import java.io.IOException + +object ApiFactory { + private const val BASE_URL: String = BuildConfig.AUTH_BASE_URL + lateinit var userPreference: UserPreference + var isInitialized = false + private set + + fun initializeUserPreference(userPreference: UserPreference) { + ApiFactory.userPreference = userPreference + } + + val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(provideOkHttpClient()) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + private fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(HeaderInterceptor()) + .build() + + class HeaderInterceptor : Interceptor { + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + if (!ApiFactory::userPreference.isInitialized) { + throw IllegalStateException("UserPreference is not initialized") + } + + val newRequest = chain.request().newBuilder() + .addHeader("memberId", userPreference.getUserId().toString()) + .build() + Log.d("userPreference API", "${userPreference.getUserId().toString()}") + return chain.proceed(newRequest) + } + } + + inline fun create(): T = retrofit.create(T::class.java) +} + +object ServicePool { + val authService = ApiFactory.create() + val userService = ApiFactory.create() +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/AuthService.kt b/app/src/main/java/com/sopt/now/compose/data/AuthService.kt new file mode 100644 index 0000000..b024983 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/AuthService.kt @@ -0,0 +1,20 @@ +package com.sopt.now.compose.data + +import com.sopt.now.compose.data.dto.request.RequestLoginDto +import com.sopt.now.compose.data.dto.request.RequestSignUpDto +import com.sopt.now.compose.data.dto.response.ResponseAuthDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthService { + @POST("member/join") + fun signUp( + @Body request: RequestSignUpDto, + ): Call + + @POST("member/login") + fun login( + @Body request: RequestLoginDto, + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/BaseState.kt b/app/src/main/java/com/sopt/now/compose/data/BaseState.kt new file mode 100644 index 0000000..846152a --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/BaseState.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BaseState( + @SerialName("isSuccess") + val isSuccess: Boolean, + @SerialName("message") + val message: String +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/Friend.kt b/app/src/main/java/com/sopt/now/compose/data/Friend.kt new file mode 100644 index 0000000..34477b3 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/Friend.kt @@ -0,0 +1,14 @@ +package com.sopt.now.compose.data + +import androidx.annotation.DrawableRes + +data class Friend( + @DrawableRes val profileImage: Int, + val name: String, + val phone: String, +) { + companion object { + const val TYPE_USER = 0 + const val TYPE_FRIEND = 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/UserPreference.kt b/app/src/main/java/com/sopt/now/compose/data/UserPreference.kt new file mode 100644 index 0000000..74afb8c --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/UserPreference.kt @@ -0,0 +1,55 @@ +package com.sopt.now.compose.data + +import android.content.Context + +import androidx.core.content.edit +import com.sopt.now.compose.data.UserPreference.UserPreferenceKeys.KEY_USER_ID +import com.sopt.now.compose.data.UserPreference.UserPreferenceKeys.KEY_USER_NAME +import com.sopt.now.compose.data.UserPreference.UserPreferenceKeys.KEY_USER_PHONE +import com.sopt.now.compose.data.UserPreference.UserPreferenceKeys.PREF_USER_DATA + +class UserPreference(context: Context) { + private val sharedPreferences = context.applicationContext.getSharedPreferences(PREF_USER_DATA, Context.MODE_PRIVATE) + + // 사용자 아이디 저장 + fun saveUserId(userId: String) { + sharedPreferences.edit { + putString(KEY_USER_ID, userId) + } + } + + // 사용자 아이디 가져오기 + fun getUserId(): String? { + return sharedPreferences.getString(KEY_USER_ID, null) + } + + // 사용자 데이터 저장 + fun saveUserData(userData: UserData) { + sharedPreferences.edit { + putString(KEY_USER_ID, userData.userId) + putString(KEY_USER_NAME, userData.userName) + putString(KEY_USER_PHONE, userData.userPhone) + } + } + + // 사용자 데이터 가져오기 + fun getUserData(): UserData? { + with(sharedPreferences){ + val userId = getString("userId", null) + val userName = getString("userName", null) + val userPhone = getString("userPhone", null) + + return if (userId != null && userName != null && userPhone != null) { + UserData(userId, userName, userPhone) + } else { + null + } + } + } + object UserPreferenceKeys { + const val PREF_USER_DATA = "userData" + const val KEY_USER_ID = "userId" + const val KEY_USER_NAME = "userName" + const val KEY_USER_PHONE = "userPhone" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/UserService.kt b/app/src/main/java/com/sopt/now/compose/data/UserService.kt new file mode 100644 index 0000000..f189db1 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/UserService.kt @@ -0,0 +1,11 @@ +package com.sopt.now.compose.data + +import com.sopt.now.compose.data.dto.response.ResponseUserInfoDto +import retrofit2.Call +import retrofit2.http.GET + +interface UserService { + @GET("member/info") + fun userInfo( + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/dto/request/RequestLoginDto.kt b/app/src/main/java/com/sopt/now/compose/data/dto/request/RequestLoginDto.kt new file mode 100644 index 0000000..edc8e42 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/dto/request/RequestLoginDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestLoginDto( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("password") + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/dto/request/ResponseSignUpDto.kt b/app/src/main/java/com/sopt/now/compose/data/dto/request/ResponseSignUpDto.kt new file mode 100644 index 0000000..c225397 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/dto/request/ResponseSignUpDto.kt @@ -0,0 +1,16 @@ +package com.sopt.now.compose.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestSignUpDto( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("password") + val password: String, + @SerialName("nickname") + val nickname: String, + @SerialName("phone") + val phone: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/dto/response/ResponseAuthDto.kt b/app/src/main/java/com/sopt/now/compose/data/dto/response/ResponseAuthDto.kt new file mode 100644 index 0000000..070f99a --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/dto/response/ResponseAuthDto.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseAuthDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/dto/response/ResponseUserInfoDto.kt b/app/src/main/java/com/sopt/now/compose/data/dto/response/ResponseUserInfoDto.kt new file mode 100644 index 0000000..de7d723 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/dto/response/ResponseUserInfoDto.kt @@ -0,0 +1,24 @@ +package com.sopt.now.compose.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseUserInfoDto( + @SerialName("code") + val code: Int, + @SerialName("message") + val message: String, + @SerialName("data") + val data: UserInfo +) + +@Serializable +data class UserInfo( + @SerialName("authenticationId") + val authenticationId: String, + @SerialName("nickname") + val nickname: String, + @SerialName("phone") + val phone: String +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/repository/LoginRepository.kt b/app/src/main/java/com/sopt/now/compose/data/repository/LoginRepository.kt new file mode 100644 index 0000000..3cd76c7 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/repository/LoginRepository.kt @@ -0,0 +1,33 @@ +package com.sopt.now.compose.data.repository + +import com.sopt.now.compose.core.view.UiState +import com.sopt.now.compose.data.ApiFactory +import com.sopt.now.compose.data.ServicePool.authService +import com.sopt.now.compose.data.dto.request.RequestLoginDto +import com.sopt.now.compose.data.dto.response.ResponseAuthDto +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class LoginRepository { + suspend fun postLogin(request: RequestLoginDto): UiState { + return withContext(Dispatchers.IO) { + runCatching { + authService.postLogin(request) + }.fold( + { response -> + val userId = response.headers()["location"] + if (userId != null) { + ApiFactory.userPreference.saveUserId(userId) + } + UiState.Success( + response.body() ?: ResponseAuthDto( + -1, + "Empty response body" + ) + ) + }, + { UiState.Failure(it.message.toString()) } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/repository/SignUpRepository.kt b/app/src/main/java/com/sopt/now/compose/data/repository/SignUpRepository.kt new file mode 100644 index 0000000..30a22d7 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/repository/SignUpRepository.kt @@ -0,0 +1,21 @@ +package com.sopt.now.compose.data.repository + +import com.sopt.now.compose.core.view.UiState +import com.sopt.now.compose.data.ServicePool +import com.sopt.now.compose.data.dto.request.RequestSignUpDto +import com.sopt.now.compose.data.dto.response.ResponseAuthDto +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class SignUpRepository { + suspend fun postSignUp(requestSignUp: RequestSignUpDto): UiState { + return withContext(Dispatchers.IO) { + runCatching { + ServicePool.authService.postSignUp(requestSignUp) + }.fold( + { UiState.Success(it) }, + { UiState.Failure(it.message.toString()) } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/repository/UserProfileRepository.kt b/app/src/main/java/com/sopt/now/compose/data/repository/UserProfileRepository.kt new file mode 100644 index 0000000..257326d --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/repository/UserProfileRepository.kt @@ -0,0 +1,32 @@ +package com.sopt.now.compose.data.repository + +import com.sopt.now.compose.core.view.UiState +import com.sopt.now.compose.data.ServicePool +import com.sopt.now.compose.data.dto.response.ResponseFriendDto +import com.sopt.now.compose.data.dto.response.ResponseUserInfoDto +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class UserProfileRepository { + suspend fun getUserProfile(): UiState { + return withContext(Dispatchers.IO) { + runCatching { + ServicePool.userService.getUserInfo() + }.fold( + { UiState.Success(it) }, + { UiState.Failure(it.message.toString()) } + ) + } + } + + suspend fun getFriendProfile(): UiState { + return withContext(Dispatchers.IO) { + runCatching { + ServicePool.friendService.getFriendInfo(1) + }.fold( + { UiState.Success(it) }, + { UiState.Failure(it.message.toString()) } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/data/userData.kt b/app/src/main/java/com/sopt/now/compose/data/userData.kt new file mode 100644 index 0000000..e5bb0e4 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/data/userData.kt @@ -0,0 +1,12 @@ +package com.sopt.now.compose.data + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable + +@Serializable +data class UserData( + val userId: String, + val userName: String, + val userPhone: String +) \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/HomeView.kt b/app/src/main/java/com/sopt/now/compose/presentation/HomeView.kt similarity index 92% rename from app/src/main/java/com/sopt/now/compose/HomeView.kt rename to app/src/main/java/com/sopt/now/compose/presentation/HomeView.kt index 7534e1b..2538b5a 100644 --- a/app/src/main/java/com/sopt/now/compose/HomeView.kt +++ b/app/src/main/java/com/sopt/now/compose/presentation/HomeView.kt @@ -1,4 +1,4 @@ -package com.sopt.now.compose +package com.sopt.now.compose.presentation import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -8,16 +8,17 @@ import com.sopt.now.compose.item.FriendItem import com.sopt.now.compose.item.Profile import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import com.sopt.now.compose.R import com.sopt.now.compose.item.UserItem import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme @Composable -fun HomeView() { +fun HomeView(userName: String, userPhone: String) { LazyColumn( modifier = Modifier.fillMaxSize() ) { item { - UserItem(friendList.first()) + UserItem(Profile(R.drawable.iv_user_profile, userName, userPhone)) } // 첫 번째 요소는 건너뛰고 적용 @@ -101,6 +102,6 @@ val friendList = listOf( @Composable fun HomePreview() { NOWSOPTAndroidTheme { - HomeView() + HomeView("","") } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/LoginActivity.kt b/app/src/main/java/com/sopt/now/compose/presentation/LoginActivity.kt similarity index 80% rename from app/src/main/java/com/sopt/now/compose/LoginActivity.kt rename to app/src/main/java/com/sopt/now/compose/presentation/LoginActivity.kt index d4f42de..240797a 100644 --- a/app/src/main/java/com/sopt/now/compose/LoginActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/presentation/LoginActivity.kt @@ -1,4 +1,4 @@ -package com.sopt.now.compose +package com.sopt.now.compose.presentation import android.content.Context import android.content.Intent @@ -32,9 +32,16 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.sopt.now.compose.R +import com.sopt.now.compose.data.ApiFactory +import com.sopt.now.compose.data.UserPreference +import com.sopt.now.compose.data.dto.request.RequestLoginDto import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme class LoginActivity : ComponentActivity() { + + private lateinit var userPreference: UserPreference + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { @@ -43,12 +50,13 @@ class LoginActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - val userId = intent.getStringExtra("userId").toString() - val userPw = intent.getStringExtra("userPw").toString() - val userName = intent.getStringExtra("userName").toString() - val userDescription = intent.getStringExtra("userDescription").toString() + userPreference = UserPreference(LocalContext.current) + if (!ApiFactory.isInitialized) { + ApiFactory.initializeUserPreference(userPreference) + } - LoginView(userId, userPw, userName ,userDescription) + val viewModel: LoginViewModel = remember { LoginViewModel() } + LoginView(viewModel) } } } @@ -57,15 +65,13 @@ class LoginActivity : ComponentActivity() { @Composable fun LoginView( - userId: String, - userPw: String, - userName: String, - userDescription: String + viewModel: LoginViewModel ) { val context = LocalContext.current var inputId by remember { mutableStateOf("") } var inputPw by remember { mutableStateOf("") } + Column( modifier = Modifier .fillMaxSize() @@ -94,7 +100,7 @@ fun LoginView( LoginButton( text = stringResource(id = R.string.btn_login), onClick = { - checkLogin(context, userId, userPw, userName, userDescription, inputId, inputPw) + checkLogin(context, viewModel, inputId, inputPw) } ) Spacer(modifier = Modifier.height(10.dp)) @@ -155,31 +161,25 @@ fun LoginButton(text: String, onClick: () -> Unit) { private fun checkLogin( context: Context, - userId: String, - userPw: String, - userName: String, - userDescription: String, + viewModel: LoginViewModel, inputId: String, inputPw: String ) { - if (userId == inputId && userPw == inputPw) { - moveToMain(context, userId, userPw, userName, userDescription) + viewModel.login(RequestLoginDto(inputId, inputPw)) + + val userId = viewModel.userIdLiveData.value + if(userId != null){ + ApiFactory.userPreference.saveUserId(userId) + moveToMain(context) } } private fun moveToMain( context: Context, - userId: String, - userPw: String, - userName: String, - userDescription: String ) { - Intent(context, MainActivity::class.java).apply { - putExtra("userId", userId) - putExtra("userPw", userPw) - putExtra("userName", userName) - putExtra("userDescription", userDescription) - } + val intent = Intent(context, MainActivity::class.java) + context.startActivity(intent) + Toast.makeText(context, "로그인 성공!", Toast.LENGTH_SHORT).show() } @@ -187,6 +187,6 @@ private fun moveToMain( @Composable fun LoginPreview() { NOWSOPTAndroidTheme { - LoginView("","","","") + LoginView(viewModel = LoginViewModel()) } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/presentation/LoginViewModel.kt b/app/src/main/java/com/sopt/now/compose/presentation/LoginViewModel.kt new file mode 100644 index 0000000..c289b72 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/presentation/LoginViewModel.kt @@ -0,0 +1,27 @@ +package com.sopt.now.compose.presentation + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sopt.now.compose.core.view.UiState +import com.sopt.now.compose.data.UserPreference +import com.sopt.now.compose.data.dto.request.RequestLoginDto +import com.sopt.now.compose.data.dto.response.ResponseAuthDto +import com.sopt.now.compose.data.repository.LoginRepository +import kotlinx.coroutines.launch + +class LoginViewModel( + private val loginRepository: LoginRepository, + private val userPreference: UserPreference +) : ViewModel() { + private val _postLoginLiveData: MutableLiveData> = MutableLiveData() + val postLoginLiveData: LiveData> = _postLoginLiveData + + fun postLogin(request: RequestLoginDto) { + viewModelScope.launch { + val result = loginRepository.postLogin(request) + _postLoginLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/MainActivity.kt b/app/src/main/java/com/sopt/now/compose/presentation/MainActivity.kt similarity index 69% rename from app/src/main/java/com/sopt/now/compose/MainActivity.kt rename to app/src/main/java/com/sopt/now/compose/presentation/MainActivity.kt index d6075a3..95d401d 100644 --- a/app/src/main/java/com/sopt/now/compose/MainActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/presentation/MainActivity.kt @@ -1,4 +1,6 @@ -package com.sopt.now.compose +package com.sopt.now.compose.presentation + +import androidx.compose.runtime.livedata.observeAsState import android.os.Bundle import androidx.activity.ComponentActivity @@ -26,6 +28,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.sopt.now.compose.BottomNavigationItem import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme class MainActivity : ComponentActivity() { @@ -37,18 +40,9 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { + val viewModel: UserInfoViewModel = remember { UserInfoViewModel() } - val userId = intent.getStringExtra("userId").toString() - val userPw = intent.getStringExtra("userPw").toString() - val userName = intent.getStringExtra("userName").toString() - val userDescription = intent.getStringExtra("userDescription").toString() - - MainView( - userId = userId, - userPw = userPw, - userName = userName, - userDescription = userDescription - ) + MainView(viewModel) } } } @@ -56,7 +50,7 @@ class MainActivity : ComponentActivity() { } @Composable -fun MainView(userId: String, userPw: String, userName: String, userDescription: String) { +fun MainView(viewModel: UserInfoViewModel) { var selectedItem by remember { mutableIntStateOf(0) } val items = listOf( BottomNavigationItem( @@ -85,33 +79,53 @@ fun MainView(userId: String, userPw: String, userName: String, userDescription: ) } } - }, + } ) { innerPadding -> Column( - modifier = Modifier - .padding(innerPadding), + modifier = Modifier.padding(innerPadding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { when(selectedItem) { - 0 -> { - HomeView() - } - 1 -> { - Text(text ="Search") - } - 2 -> { - MyPageView(userId, userPw, userName, userDescription) - } + 0 -> SetHomeView(viewModel) + 1 -> Text(text ="Search") + 2 -> SetMyPageView(viewModel) } - } } } +// 사용자 데이터 적용 +@Composable +fun SetHomeView(viewModel: UserInfoViewModel){ + + viewModel.userInfo() + + viewModel.userInfoLiveData.observeAsState().value?.let { userInfo -> + HomeView( + userName = userInfo.data.nickname, + userPhone = userInfo.data.phone, + ) + } +} + +@Composable +fun SetMyPageView(viewModel: UserInfoViewModel){ + + viewModel.userInfo() + + viewModel.userInfoLiveData.observeAsState().value?.let { userInfo -> + MyPageView( + userId = userInfo.data.authenticationId, + userPw = userInfo.data.nickname, + userPhone = userInfo.data.phone, + ) + } +} + @Preview(showBackground = true) @Composable fun MainPreview() { NOWSOPTAndroidTheme { - MainView("Id1234", "Password123", "UserName", "ISTP 입니다!") + MyPageView("", "", "") } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/MyPageView.kt b/app/src/main/java/com/sopt/now/compose/presentation/MyPageView.kt similarity index 89% rename from app/src/main/java/com/sopt/now/compose/MyPageView.kt rename to app/src/main/java/com/sopt/now/compose/presentation/MyPageView.kt index d0589b6..846d0a4 100644 --- a/app/src/main/java/com/sopt/now/compose/MyPageView.kt +++ b/app/src/main/java/com/sopt/now/compose/presentation/MyPageView.kt @@ -1,5 +1,6 @@ -package com.sopt.now.compose +package com.sopt.now.compose.presentation +import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.fillMaxWidth @@ -14,10 +15,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.sopt.now.compose.R import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme @Composable -fun MyPageView(userId: String, userPw: String, userName: String, userDescription: String) { +fun MyPageView(userId: String, userPw: String, userPhone: String) { + Log.d("myPage", "${userId}") Column( modifier = Modifier .fillMaxSize() @@ -35,7 +38,7 @@ fun MyPageView(userId: String, userPw: String, userName: String, userDescription ) Spacer(modifier = Modifier.width(20.dp)) Text( - text = userName, + text = userPhone, fontSize = 20.sp ) } @@ -52,11 +55,12 @@ fun MyPageView(userId: String, userPw: String, userName: String, userDescription Spacer(modifier = Modifier.height(30.dp)) UserInfoItem( label = stringResource(id = R.string.tv_user_description), - value = userDescription + value = userPhone ) } } + @Composable fun UserInfoItem(label: String, value: String) { Column { @@ -77,6 +81,6 @@ fun UserInfoItem(label: String, value: String) { @Composable fun MyPagePreview() { NOWSOPTAndroidTheme { - MyPageView("myPageSopt", "mypagePassword123", "MyPageUserName", "INFJ 입니다!") + MyPageView("myPageSopt", "mypagePassword123", "MyPageUserName") } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/SignUpActivity.kt b/app/src/main/java/com/sopt/now/compose/presentation/SignUpActivity.kt similarity index 83% rename from app/src/main/java/com/sopt/now/compose/SignUpActivity.kt rename to app/src/main/java/com/sopt/now/compose/presentation/SignUpActivity.kt index 50259db..0cd3a9f 100644 --- a/app/src/main/java/com/sopt/now/compose/SignUpActivity.kt +++ b/app/src/main/java/com/sopt/now/compose/presentation/SignUpActivity.kt @@ -1,4 +1,4 @@ -package com.sopt.now.compose +package com.sopt.now.compose.presentation import android.content.Context import android.content.Intent @@ -24,6 +24,11 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.sopt.now.compose.R +import com.sopt.now.compose.data.ApiFactory +import com.sopt.now.compose.data.ApiFactory.userPreference +import com.sopt.now.compose.data.UserPreference +import com.sopt.now.compose.data.dto.request.RequestSignUpDto import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme class SignUpActivity : ComponentActivity() { @@ -35,7 +40,11 @@ class SignUpActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - SignUpCompose() + userPreference = UserPreference(this) + ApiFactory.initializeUserPreference(userPreference) + + val viewModel: SignUpViewModel = remember { SignUpViewModel() } + SignUpCompose(viewModel) } } } @@ -43,7 +52,7 @@ class SignUpActivity : ComponentActivity() { } @Composable -fun SignUpCompose(){ +fun SignUpCompose(viewModel: SignUpViewModel){ val context = LocalContext.current var userId by remember { mutableStateOf("") } var userPw by remember { mutableStateOf("") } @@ -85,7 +94,7 @@ fun SignUpCompose(){ Spacer(modifier = Modifier.weight(2f)) Button( onClick = { - checkSignUp(context, userId, userPw, userName, userDescription) + checkSignUp(viewModel, context, userId, userPw, userName, userDescription) }, colors = ButtonDefaults.buttonColors(containerColor = Color.Black), shape = RoundedCornerShape(50.dp), @@ -116,16 +125,21 @@ fun TextFieldWithLabel(label: String, value: String, hint: String, onValueChange } // 회원 가입 가능 여부 체크 -fun checkSignUp(context: Context, userId: String, userPw: String, userName: String, userDescription: String) { - val isValidId = userId.length in 6..10 +fun checkSignUp( + viewModel: SignUpViewModel, + context: Context, + userId: String, + userPw: String, + userName: String, + userDescription: String) { val isValidPw = userPw.length in 8..12 val isValidName = userName.trim().isEmpty() // 공백으로만 이루어진 경우 판단 val message = when { - !isValidId -> context.getString(R.string.error_invalid_id) !isValidPw -> context.getString(R.string.error_invalid_pw) isValidName -> context.getString(R.string.error_empty_name) else -> { + viewModel.signUp(RequestSignUpDto(userId, userPw, userName, userDescription)) moveToLogin(context, userId, userPw, userName, userDescription) context.getString(R.string.signup_success) } @@ -148,6 +162,6 @@ private fun moveToLogin(context: Context, userId: String, userPw: String, userNa @Composable fun SignUpPreview() { NOWSOPTAndroidTheme { - SignUpCompose() + SignUpCompose(viewModel = SignUpViewModel()) } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/presentation/SignUpViewModel.kt b/app/src/main/java/com/sopt/now/compose/presentation/SignUpViewModel.kt new file mode 100644 index 0000000..5eeb797 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/presentation/SignUpViewModel.kt @@ -0,0 +1,26 @@ +package com.sopt.now.compose.presentation + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sopt.now.compose.core.view.UiState +import com.sopt.now.compose.data.UserPreference +import com.sopt.now.compose.data.dto.request.RequestSignUpDto +import com.sopt.now.compose.data.dto.response.ResponseAuthDto +import com.sopt.now.compose.data.repository.SignUpRepository +import kotlinx.coroutines.launch + +class SignUpViewModel( + private val signUpRepository: SignUpRepository, + private val userPreference: UserPreference +) : ViewModel() { + private val _postSignUpLiveData: MutableLiveData> = MutableLiveData() + val postSignUpLiveData: MutableLiveData> = _postSignUpLiveData + + fun postSignUp(requestSignUp: RequestSignUpDto) { + viewModelScope.launch { + val result = signUpRepository.postSignUp(requestSignUp) + _postSignUpLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/presentation/UserInfoViewModel.kt b/app/src/main/java/com/sopt/now/compose/presentation/UserInfoViewModel.kt new file mode 100644 index 0000000..812278c --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/presentation/UserInfoViewModel.kt @@ -0,0 +1,62 @@ +package com.sopt.now.compose.presentation + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.google.gson.Gson +import com.sopt.now.compose.data.BaseState +import com.sopt.now.compose.data.ServicePool +import com.sopt.now.compose.data.dto.response.ResponseAuthDto +import com.sopt.now.compose.data.dto.response.ResponseUserInfoDto +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class UserInfoViewModel : ViewModel() { + private val userService by lazy { ServicePool.userService } + val liveData = MutableLiveData() + val userInfoLiveData = MutableLiveData() + + fun userInfo() { + userService.userInfo().enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response, + ) { + if (response.isSuccessful) { + val data: ResponseUserInfoDto? = response.body() + val userId = response.headers()["location"] + userInfoLiveData.value = data + + liveData.value = BaseState( + isSuccess = true, + message = response.message() + ) + Log.d("UserInfo", "data: $data, userId: $userId") + } else { + val error = response.errorBody()?.string() + val gson = Gson() + try { + val errorResponse = gson.fromJson(error, ResponseAuthDto::class.java) + liveData.value = BaseState( + isSuccess = false, + message = "회원 정보 조회 실패: ${errorResponse.message}" // 에러 메시지 사용 + ) + } catch (e: Exception) { + liveData.value = BaseState( + isSuccess = false, + message = "회원 정보 조회 실패: 에러 메시지 파싱 실패" + ) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + liveData.value = BaseState( + isSuccess = false, + message = "서버 에러" + ) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/presentation/UserProfileViewModel.kt b/app/src/main/java/com/sopt/now/compose/presentation/UserProfileViewModel.kt new file mode 100644 index 0000000..461fc68 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/presentation/UserProfileViewModel.kt @@ -0,0 +1,43 @@ +package com.sopt.now.compose.presentation + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sopt.now.compose.core.view.UiState +import com.sopt.now.compose.data.dto.response.ResponseFriendDto +import com.sopt.now.compose.data.dto.response.ResponseUserInfoDto +import com.sopt.now.compose.data.repository.UserProfileRepository +import kotlinx.coroutines.launch + +class UserProfileViewModel( + private val userProfileRepository: UserProfileRepository +) : ViewModel() { + private val _getUserProfileLiveData = MutableLiveData>() + val getUserInfoProfileLiveData: MutableLiveData> = + _getUserProfileLiveData + + private val _getFriendProfileLiveData = MutableLiveData>() + val getFriendProfileLiveData: MutableLiveData> = + _getFriendProfileLiveData + + init { + getUserProfile() + getFriendProfile() + } + + // 사용자 프로필 가져오기 + private fun getUserProfile() { + viewModelScope.launch { + val result = userProfileRepository.getUserProfile() + _getUserProfileLiveData.value = result + } + } + + // 친구 프로필 가져오기 + private fun getFriendProfile() { + viewModelScope.launch { + val result = userProfileRepository.getFriendProfile() + _getFriendProfileLiveData.value = result + } + } +} \ No newline at end of file