diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ea3ec0..d995784 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -7,6 +9,10 @@ plugins { id("kotlin-kapt") // kapt 플러그인 추가 } +val localProperties = Properties().apply { + load(project.rootProject.file("local.properties").inputStream()) +} + android { namespace = "org.sopt.and" compileSdk = 34 @@ -18,6 +24,7 @@ android { versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String","BASE_URL", localProperties["base.url"].toString()) } buildTypes { @@ -38,6 +45,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -56,12 +64,6 @@ dependencies { implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.androidx.compose.navigation) - implementation(libs.kotlinx.serialization.json) - // Hilt 관련 의존성 추가 - implementation(libs.hilt.android) - implementation(libs.hilt.navigation.compose) - implementation(libs.androidx.appcompat) - kapt(libs.hilt.compiler) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -69,4 +71,16 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + // Hilt 관련 의존성 추가 + implementation(libs.hilt.android) + implementation(libs.hilt.navigation.compose) + implementation(libs.androidx.appcompat) + kapt(libs.hilt.compiler) + // Network + implementation(platform(libs.okhttp.bom)) + implementation(libs.okhttp) + implementation(libs.okhttp.logging.interceptor) + implementation(libs.retrofit) + implementation(libs.retrofit.kotlin.serialization.converter) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e336060..acf3a2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + ( + @SerialName("result") val result: T +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/model/request/LoginRequestDto.kt b/app/src/main/java/org/sopt/and/data/remote/model/request/LoginRequestDto.kt new file mode 100644 index 0000000..e485a64 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/model/request/LoginRequestDto.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.remote.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequestDto( + @SerialName("username") val username: String, + @SerialName("password") val password: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/model/request/UserRegistrationRequestDto.kt b/app/src/main/java/org/sopt/and/data/remote/model/request/UserRegistrationRequestDto.kt new file mode 100644 index 0000000..1c6839d --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/model/request/UserRegistrationRequestDto.kt @@ -0,0 +1,11 @@ +package org.sopt.and.data.remote.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserRegistrationRequestDto ( + @SerialName("username") val username: String, + @SerialName("password") val password: String, + @SerialName("hobby") val hobby: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/model/response/GetMyHobbyResponseDto.kt b/app/src/main/java/org/sopt/and/data/remote/model/response/GetMyHobbyResponseDto.kt new file mode 100644 index 0000000..e1f2d5b --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/model/response/GetMyHobbyResponseDto.kt @@ -0,0 +1,9 @@ +package org.sopt.and.data.remote.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetMyHobbyResponseDto( + @SerialName("hobby") val hobby: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/model/response/LoginResponseDto.kt b/app/src/main/java/org/sopt/and/data/remote/model/response/LoginResponseDto.kt new file mode 100644 index 0000000..bfcf071 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/model/response/LoginResponseDto.kt @@ -0,0 +1,9 @@ +package org.sopt.and.data.remote.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginResponseDto ( + @SerialName("token") val token: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/model/response/UserRegistrationResponseDto.kt b/app/src/main/java/org/sopt/and/data/remote/model/response/UserRegistrationResponseDto.kt new file mode 100644 index 0000000..0562db1 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/model/response/UserRegistrationResponseDto.kt @@ -0,0 +1,9 @@ +package org.sopt.and.data.remote.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserRegistrationResponseDto( + @SerialName("no") val no: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/service/AuthService.kt b/app/src/main/java/org/sopt/and/data/remote/service/AuthService.kt new file mode 100644 index 0000000..960af66 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/service/AuthService.kt @@ -0,0 +1,21 @@ +package org.sopt.and.data.remote.service + +import org.sopt.and.data.remote.model.request.LoginRequestDto +import org.sopt.and.data.remote.model.request.UserRegistrationRequestDto +import org.sopt.and.data.remote.model.response.LoginResponseDto +import org.sopt.and.data.remote.model.response.UserRegistrationResponseDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthService { + @POST("user") + fun registerUser( + @Body userRegistrationRequestDto: UserRegistrationRequestDto + ): Call + + @POST("login") + fun login( + @Body loginRequestDto: LoginRequestDto + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/service/UserService.kt b/app/src/main/java/org/sopt/and/data/remote/service/UserService.kt new file mode 100644 index 0000000..5653f60 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/service/UserService.kt @@ -0,0 +1,13 @@ +package org.sopt.and.data.remote.service + +import org.sopt.and.data.remote.model.response.GetMyHobbyResponseDto +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header + +interface UserService { + @GET("user/my-hobby") + fun getMyHobby( + @Header("token") token: String + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/TokenRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/TokenRepositoryImpl.kt new file mode 100644 index 0000000..eab9008 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/TokenRepositoryImpl.kt @@ -0,0 +1,19 @@ +package org.sopt.and.data.repositoryimpl + +import org.sopt.and.data.local.TokenLocalDataSource +import org.sopt.and.domain.repository.TokenRepository +import javax.inject.Inject + +class TokenRepositoryImpl @Inject constructor( + private val tokenLocalDataSource: TokenLocalDataSource +) : TokenRepository { + override fun getToken(): String = tokenLocalDataSource.token + + override fun setToken(token: String) { + tokenLocalDataSource.token = token + } + + override fun removeToken() { + tokenLocalDataSource.removeToken() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/di/ApiFactory.kt b/app/src/main/java/org/sopt/and/di/ApiFactory.kt new file mode 100644 index 0000000..67532d3 --- /dev/null +++ b/app/src/main/java/org/sopt/and/di/ApiFactory.kt @@ -0,0 +1,38 @@ +package org.sopt.and.di + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.sopt.and.BuildConfig +import org.sopt.and.data.remote.service.AuthService +import org.sopt.and.data.remote.service.UserService +import retrofit2.Retrofit + +object ApiFactory { + private const val BASE_URL: String = BuildConfig.BASE_URL + + private val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + private val client = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + + val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + } + + inline fun 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/org/sopt/and/di/RepositoryModule.kt b/app/src/main/java/org/sopt/and/di/RepositoryModule.kt new file mode 100644 index 0000000..5994e15 --- /dev/null +++ b/app/src/main/java/org/sopt/and/di/RepositoryModule.kt @@ -0,0 +1,19 @@ +package org.sopt.and.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.and.domain.repository.TokenRepository +import org.sopt.and.data.repositoryimpl.TokenRepositoryImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + @Binds + @Singleton + abstract fun bindTokenRepository( + tokenRepositoryImpl: TokenRepositoryImpl + ): TokenRepository +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/di/SharedPreferencesModule.kt b/app/src/main/java/org/sopt/and/di/SharedPreferencesModule.kt new file mode 100644 index 0000000..01336f3 --- /dev/null +++ b/app/src/main/java/org/sopt/and/di/SharedPreferencesModule.kt @@ -0,0 +1,20 @@ +package org.sopt.and.di + +import android.content.Context +import android.content.SharedPreferences +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object SharedPreferencesModule { + @Provides + @Singleton + fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences { + return context.getSharedPreferences("token_prefs", Context.MODE_PRIVATE) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/di/TokenDataStoreModule.kt b/app/src/main/java/org/sopt/and/di/TokenDataStoreModule.kt new file mode 100644 index 0000000..3eb6c9a --- /dev/null +++ b/app/src/main/java/org/sopt/and/di/TokenDataStoreModule.kt @@ -0,0 +1,20 @@ +package org.sopt.and.di + +import android.content.SharedPreferences +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.and.data.local.TokenLocalDataSource +import org.sopt.and.data.local.TokenLocalDataSourceImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object TokenLocalDataSourceModule { + @Provides + @Singleton + fun provideTokenLocalDataSource(sharedPreferences: SharedPreferences): TokenLocalDataSource { + return TokenLocalDataSourceImpl(sharedPreferences) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/domain/User.kt b/app/src/main/java/org/sopt/and/domain/User.kt new file mode 100644 index 0000000..67756cb --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/User.kt @@ -0,0 +1,7 @@ +package org.sopt.and.domain + +data class User( + val name: String, + val password: String, + val hobby: String +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/domain/repository/TokenRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/TokenRepository.kt new file mode 100644 index 0000000..b979642 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/TokenRepository.kt @@ -0,0 +1,7 @@ +package org.sopt.and.domain.repository + +interface TokenRepository { + fun getToken(): String + fun setToken(token: String) + fun removeToken() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/model/User.kt b/app/src/main/java/org/sopt/and/presentation/model/User.kt deleted file mode 100644 index cd0abc2..0000000 --- a/app/src/main/java/org/sopt/and/presentation/model/User.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.sopt.and.presentation.model - -import org.sopt.and.presentation.ui.auth.screen.SignUpState - -data class User( - val email: String, - val password: String, -) { - companion object { - val EMAIL_VALIDATION_REGEX = "^[a-zA-Z0 -9]+@[a-zA-Z0-9.]+\\.[a-zA-Z]{2,}\$".toRegex() - - const val PW_MIN_LENGTH = 8 - const val PW_MAX_LENGTH = 20 - const val PW_MIN_TYPE_COUNT = 3 - val UPPER_CASE_REGEX = "[A-Z]".toRegex() - val LOWER_CASE_REGEX = "[a-z]".toRegex() - val DIGIT_REGEX = "[0-9]".toRegex() - val SPECIAL_CHAR_REGEX = "[!@#\$%^&*(),.?\":{}|<>]".toRegex() - } -} - -fun User.validateSignUp(): SignUpState { - return when { - !isSignUpEmailValidate() -> SignUpState.EmailInvalid - !isSignUpPasswordValidate() -> SignUpState.PasswordInvalid - else -> SignUpState.Success - } -} - -fun User.isSignUpEmailValidate(): Boolean { - return email.matches(User.EMAIL_VALIDATION_REGEX) -} - -fun User.isSignUpPasswordValidate(): Boolean { - val count = listOf( - User.UPPER_CASE_REGEX.containsMatchIn(password), - User.LOWER_CASE_REGEX.containsMatchIn(password), - User.DIGIT_REGEX.containsMatchIn(password), - User.SPECIAL_CHAR_REGEX.containsMatchIn(password) - ).count { it } - - return password.length in User.PW_MIN_LENGTH..User.PW_MAX_LENGTH && count >= User.PW_MIN_TYPE_COUNT -} diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/navigation/AuthNavGraph.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/navigation/AuthNavGraph.kt index 65b5223..7457aa4 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/navigation/AuthNavGraph.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/navigation/AuthNavGraph.kt @@ -4,15 +4,13 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.navigation -import kotlinx.serialization.Serializable -import org.sopt.and.presentation.ui.auth.screen.AuthViewModel import org.sopt.and.presentation.ui.auth.screen.SignInRoute import org.sopt.and.presentation.ui.auth.screen.SignUpRoute +import org.sopt.and.presentation.ui.main.navigation.navigateToMain import org.sopt.and.presentation.ui.navigation.WavveRoute fun NavGraphBuilder.authNavGraph( navController: NavHostController, - authViewModel: AuthViewModel, ) { navigation( startDestination = WavveRoute.SIGN_IN, @@ -20,15 +18,15 @@ fun NavGraphBuilder.authNavGraph( ) { composable(route = WavveRoute.SIGN_IN) { SignInRoute( - navController = navController, - authViewModel = authViewModel, + navigateToSignUp = { navController.navigateToSignUp() }, + navigateToMain = { navController.navigateToMain() }, ) } composable(route = WavveRoute.SIGN_UP) { SignUpRoute( - navController = navController, - authViewModel = authViewModel, + navigateToSignIn = { navController.navigateToSignIn() }, + navigateToBack = { navController.popBackStack() } ) } } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthState.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthState.kt index dbbe130..07882d8 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthState.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthState.kt @@ -1,17 +1,17 @@ package org.sopt.and.presentation.ui.auth.screen +import org.sopt.and.data.remote.model.response.UserRegistrationResponseDto + sealed class SignInState { data object Idle : SignInState() data object EmailEmpty : SignInState() data object PasswordEmpty : SignInState() - data object EmailInvalid : SignInState() - data object PasswordInvalid : SignInState() data object Success : SignInState() + data class Failure(val errorMessage: String) : SignInState() } sealed class SignUpState { - data object Idle: SignUpState() - data object EmailInvalid: SignUpState() - data object PasswordInvalid: SignUpState() - data object Success: SignUpState() + data object Idle : SignUpState() + data class Success(val response: UserRegistrationResponseDto?) : SignUpState() + data class Failure(val errorMessage: String) : SignUpState() } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt index 301fd42..ff9096a 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/AuthViewModel.kt @@ -1,39 +1,106 @@ package org.sopt.and.presentation.ui.auth.screen +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.and.presentation.model.User -import org.sopt.and.presentation.model.validateSignUp +import kotlinx.coroutines.launch +import org.sopt.and.data.remote.model.request.LoginRequestDto +import org.sopt.and.data.remote.model.request.UserRegistrationRequestDto +import org.sopt.and.data.remote.model.response.LoginResponseDto +import org.sopt.and.data.remote.model.response.UserRegistrationResponseDto +import org.sopt.and.domain.repository.TokenRepository +import org.sopt.and.di.ServicePool +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response import javax.inject.Inject @HiltViewModel -class AuthViewModel @Inject constructor() : ViewModel() { - private val _user = MutableLiveData() - val user: LiveData = _user +class AuthViewModel @Inject constructor( + private val tokenRepository: TokenRepository, +) : ViewModel() { + private val authService by lazy { ServicePool.authService } private val _signInState = MutableStateFlow(SignInState.Idle) - val signInState : StateFlow = _signInState + val signInState: StateFlow = _signInState + + private val _signInUserName = MutableLiveData("") + val signInUserName: LiveData = _signInUserName + + private val _signInPassword = MutableLiveData("") + val signInPassword: LiveData = _signInPassword private val _signUpState = MutableStateFlow(SignUpState.Idle) - val signUpState : StateFlow = _signUpState + val signUpState: StateFlow = _signUpState + + private val _signUpUserName = MutableLiveData("") + val signUpUserName: LiveData = _signUpUserName + + private val _signUpPassword = MutableLiveData("") + val signUpPassword: LiveData = _signUpPassword + + private val _signUpHobby = MutableLiveData("") + val signUpHobby: LiveData = _signUpHobby + + fun validateSignIn(inputEmail: String, inputPassword: String) { - _signInState.value = when { - inputEmail.isEmpty() -> SignInState.EmailEmpty - inputPassword.isEmpty() -> SignInState.PasswordEmpty - _user.value?.email != inputEmail -> SignInState.EmailInvalid - _user.value?.password != inputPassword -> SignInState.PasswordInvalid - else -> SignInState.Success + viewModelScope.launch { + authService.login(LoginRequestDto(inputEmail, inputPassword)).enqueue(object : + Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + _signInState.value = SignInState.Success + tokenRepository.setToken(token = response.body()!!.token) + Log.d("token", tokenRepository.getToken()) + } else { + _signInState.value = SignInState.Failure(response.message()) + } + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + _signInState.value = SignInState.Failure(t.message.toString()) + } + } + ) } } - fun validateSignUp(email: String, password: String) { - val authInfo = User(email, password) - _signUpState.value = authInfo.validateSignUp() - if (_signUpState.value is SignUpState.Success) _user.value = authInfo + fun validateSignUp(email: String, password: String, hobby: String) { + viewModelScope.launch { + authService.registerUser(UserRegistrationRequestDto(email, password, hobby)) + .enqueue(object : + Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + _signUpState.value = SignUpState.Success(response.body()) + } else { + _signUpState.value = SignUpState.Failure(response.message()) + } + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + _signUpState.value = SignUpState.Failure(t.message.toString()) + } + } + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt index c4fdae4..2302604 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignInScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -36,42 +35,33 @@ 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 androidx.navigation.NavHostController +import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.and.R import org.sopt.and.presentation.ui.auth.component.AuthTextField -import org.sopt.and.presentation.ui.auth.component.SocialPlatformList import org.sopt.and.presentation.ui.auth.component.SocialPlatformIconRow -import org.sopt.and.presentation.ui.auth.navigation.navigateToSignUp -import org.sopt.and.presentation.ui.main.navigation.navigateToMain -import org.sopt.and.presentation.utils.showToast +import org.sopt.and.presentation.ui.auth.component.SocialPlatformList +import org.sopt.and.presentation.util.showToast import org.sopt.and.ui.theme.ANDANDROIDTheme @Composable fun SignInRoute( - navController: NavHostController, - authViewModel: AuthViewModel, + authViewModel: AuthViewModel = hiltViewModel(), + navigateToSignUp: () -> Unit, + navigateToMain: () -> Unit, ) { val context = LocalContext.current val signInState by authViewModel.signInState.collectAsState() - val onSignUpClick = { navController.navigateToSignUp() } val onSignInClick: (String, String) -> Unit = { email, password -> authViewModel.validateSignIn(email, password) - } - - LaunchedEffect(signInState) { when (signInState) { - is SignInState.EmailEmpty -> showToast(context = context, message = "이메일을 입력하세요") - is SignInState.PasswordEmpty -> showToast(context = context, message = "비밀번호를 입력하세요") - is SignInState.EmailInvalid -> showToast(context = context, message = "이메일이 일치하지 않습니다") - is SignInState.PasswordInvalid -> showToast( - context = context, - message = "비밀번호가 일치하지 않습니다" - ) - is SignInState.Success -> { - showToast(context = context, message = "로그인에 성공했습니다") - navController.navigateToMain(authViewModel.user.value?.email.orEmpty()) + showToast(context = context, message = "로그인에 성공했습니다.") + navigateToMain() + } + + is SignInState.Failure -> { + showToast(context = context, message = "로그인에 실패했습니다.") } else -> {} @@ -79,7 +69,7 @@ fun SignInRoute( } SignInScreen( - onSignUpClick = onSignUpClick, + onSignUpClick = navigateToSignUp, onSignInClick = onSignInClick, ) } @@ -176,7 +166,7 @@ fun SignInScreen( ) SignInOption( text = "회원가입", - onClick = { onSignUpClick() } + onClick = onSignUpClick ) } @@ -255,8 +245,6 @@ fun SignInOption( ) } - - @Preview(showBackground = true) @Composable fun SignInPreview() { diff --git a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt index 3f955b9..0828948 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/auth/screen/SignUpScreen.kt @@ -33,60 +33,56 @@ 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 androidx.navigation.NavHostController +import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.and.R import org.sopt.and.presentation.ui.auth.component.AuthTextField -import org.sopt.and.presentation.ui.auth.component.SocialPlatformList import org.sopt.and.presentation.ui.auth.component.SocialPlatformIconRow -import org.sopt.and.presentation.ui.auth.navigation.navigateToSignIn -import org.sopt.and.presentation.utils.showToast +import org.sopt.and.presentation.ui.auth.component.SocialPlatformList +import org.sopt.and.presentation.util.showToast import org.sopt.and.ui.theme.ANDANDROIDTheme @Composable fun SignUpRoute( - authViewModel: AuthViewModel, - navController: NavHostController, + authViewModel: AuthViewModel = hiltViewModel(), + navigateToSignIn: () -> Unit, + navigateToBack: () -> Unit, ) { val context = LocalContext.current val signUpState by authViewModel.signUpState.collectAsState() - val onSignUpClick: (String, String) -> Unit = { email, password -> - authViewModel.validateSignUp(email, password) + val onSignUpClick: (String, String, String) -> Unit = { username, password, hobby -> + authViewModel.validateSignUp(username, password, hobby) when (signUpState) { - is SignUpState.EmailInvalid -> showToast( - context = context, - message = "아이디가 잘못된 형식입니다" - ) - - is SignUpState.PasswordInvalid -> showToast( - context = context, - message = "비밀번호가 잘못된 형식입니다" - ) - is SignUpState.Success -> { - showToast(context = context, message = "회원가입에 성공했습니다.") - navController.navigateToSignIn() + showToast( + context = context, + message = "회원가입에 성공했습니다. 회원번호는 ${(signUpState as SignUpState.Success).response?.result?.no}입니다." + ) + navigateToSignIn() + } + + is SignUpState.Failure -> { + showToast(context = context, message = "회원가입에 실패하였습니다.") } else -> {} } } - val onCancelClick: () -> Unit = { navController.popBackStack() } SignUpScreen( onSignUpClick = onSignUpClick, - onCancelClick = onCancelClick, + onCancelClick = navigateToBack, ) } @Composable fun SignUpScreen( - onSignUpClick: (String, String) -> Unit, + onSignUpClick: (String, String, String) -> Unit, onCancelClick: () -> Unit, ) { - var inputEmail by remember { mutableStateOf("") } var inputPassword by remember { mutableStateOf("") } + var inputHobby by remember { mutableStateOf("") } Column( modifier = Modifier @@ -157,7 +153,7 @@ fun SignUpScreen( .fillMaxWidth() .clip(shape = RoundedCornerShape(4.dp)) .background(color = Color(0xFF262626)), - hint = "wavve@example.com" + hint = "sonny" ) Spacer(modifier = Modifier.height(8.dp)) Row( @@ -213,6 +209,36 @@ fun SignUpScreen( ) } + AuthTextField( + value = inputHobby, + onValueChange = { newValue -> inputHobby = newValue }, + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(4.dp)) + .background(color = Color(0xFF262626)), + hint = "basket" + ) + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.img_signup_caution), + contentDescription = "", + modifier = Modifier + .align(Alignment.Top) + .padding(2.dp) + .size(14.dp) + ) + Text( + text = "8자 미만으로 작성해주세요.", + fontSize = 12.sp, + lineHeight = 14.sp, + color = Color(0xFF848484) + ) + } + Spacer(modifier = Modifier.height(40.dp)) Row( @@ -282,7 +308,7 @@ fun SignUpScreen( .fillMaxWidth() .background(color = Color(0xFF0F42C7)) .clickable( - onClick = { onSignUpClick(inputEmail, inputPassword) } + onClick = { onSignUpClick(inputEmail, inputPassword, inputHobby) } ), contentAlignment = Alignment.Center ) { diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt index 8a7a6c2..cca39d2 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavGraph.kt @@ -2,30 +2,36 @@ package org.sopt.and.presentation.ui.main.navigation import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController -import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.navArgument +import androidx.navigation.navigation +import org.sopt.and.presentation.ui.main.screen.HomeScreen import org.sopt.and.presentation.ui.main.screen.MainRoute -import org.sopt.and.presentation.ui.main.screen.MainViewModel -import org.sopt.and.presentation.ui.navigation.KeyStorage +import org.sopt.and.presentation.ui.main.screen.MyPageScreen +import org.sopt.and.presentation.ui.main.screen.SearchScreen import org.sopt.and.presentation.ui.navigation.WavveRoute fun NavGraphBuilder.mainNavGraph( navController: NavHostController, - mainViewModel: MainViewModel, ) { - composable( - route = "${WavveRoute.MAIN}/{${KeyStorage.USER_EMAIL}}", - arguments = listOf( - navArgument(KeyStorage.USER_EMAIL) { type = NavType.StringType } - ) - ) { navBackStackEntry -> - val userEmail = - navBackStackEntry.arguments?.getString(KeyStorage.USER_EMAIL) ?: "unknown@example.com" - MainRoute( - navController = navController, - mainViewModel = mainViewModel, - userEmail = userEmail, - ) + navigation( + startDestination = WavveRoute.MAIN, + route = WavveRoute.WAVVE + ) { + composable(route = WavveRoute.MAIN) { + MainRoute( + navigateToHome = { navController.navigateToHome() }, + navigateToSearch = { navController.navigateToSearch() }, + navigateToMy = { navController.navigateToMy() } + ) + } + composable(route = WavveRoute.HOME) { + HomeScreen() + } + composable(route = WavveRoute.SEARCH) { + SearchScreen() + } + composable(route = WavveRoute.MY) { + MyPageScreen(userHobby = "") + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt index d814e55..f4b2028 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/navigation/MainNavigator.kt @@ -3,8 +3,18 @@ package org.sopt.and.presentation.ui.main.navigation import androidx.navigation.NavController import org.sopt.and.presentation.ui.navigation.WavveRoute -fun NavController.navigateToMain( - userEmail: String, -) { - navigate("${WavveRoute.MAIN}/$userEmail") +fun NavController.navigateToMain() { + navigate(WavveRoute.MAIN) +} + +fun NavController.navigateToHome() { + navigate(WavveRoute.HOME) +} + +fun NavController.navigateToSearch() { + navigate(WavveRoute.SEARCH) +} + +fun NavController.navigateToMy() { + navigate(WavveRoute.MY) } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt index 98847cd..2d8b56e 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/HomeScreen.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -43,14 +42,11 @@ import org.sopt.and.presentation.ui.main.component.todayTop20Images import org.sopt.and.ui.theme.ANDANDROIDTheme @Composable -fun HomeScreen( - paddingValues: PaddingValues -) { +fun HomeScreen() { LazyColumn( modifier = Modifier .fillMaxSize() .background(color = Color(0xFF161616)) - .padding(paddingValues) .padding(10.dp), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -194,8 +190,6 @@ fun HomeHorizontalBanner( @Composable fun ShowHomeScreen() { ANDANDROIDTheme { - HomeScreen( - paddingValues = PaddingValues(0.dp) - ) + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt index 9982195..7bd8cfd 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainScreen.kt @@ -1,8 +1,10 @@ package org.sopt.and.presentation.ui.main.screen import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.NavigationBar @@ -11,6 +13,8 @@ import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -24,24 +28,31 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController +import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.and.R import org.sopt.and.ui.theme.ANDANDROIDTheme @Composable fun MainRoute( - navController: NavHostController, - mainViewModel: MainViewModel, - userEmail: String, + mainViewModel: MainViewModel = hiltViewModel(), + navigateToHome: () -> Unit, + navigateToSearch: () -> Unit, + navigateToMy: () -> Unit, ) { + val userHobbyState by mainViewModel.userHobbyState.collectAsState() + + LaunchedEffect(Unit) { + mainViewModel.getUserHobby() + } + MainScreen( - userEmail = userEmail + userHobbyState = userHobbyState, ) } @Composable fun MainScreen( - userEmail: String + userHobbyState: UserHobbyState, ) { var selectedTab by remember { mutableStateOf(MainTabList.HOME) } val onTabSelected: (MainTabList) -> Unit = { tab -> @@ -127,23 +138,39 @@ fun MainScreen( containerColor = Color.Black, contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp), content = { innerPadding -> - // selectedTab 값에 따라 표시되는 화면을 변경 - when (selectedTab) { - MainTabList.HOME -> HomeScreen(innerPadding) - MainTabList.SEARCH -> SearchScreen(innerPadding) - MainTabList.MY -> MyPageScreen( - paddingValues = innerPadding, - userEmail = userEmail - ) + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + when (selectedTab) { + MainTabList.HOME -> HomeScreen() + MainTabList.SEARCH -> SearchScreen() + MainTabList.MY -> { + when (userHobbyState) { + is UserHobbyState.Success -> { + val userHobby = userHobbyState.hobby + MyPageScreen(userHobby = userHobby) + } + + is UserHobbyState.Failure -> { + val errorMessage = userHobbyState.errorMessage + } + + else -> {} + } + } + } } + } ) } sealed class MainTabList { - data object HOME: MainTabList() - data object SEARCH: MainTabList() - data object MY: MainTabList() + data object HOME : MainTabList() + data object SEARCH : MainTabList() + data object MY : MainTabList() } diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt index 6c77450..214b2f5 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MainViewModel.kt @@ -1,11 +1,52 @@ package org.sopt.and.presentation.ui.main.screen import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import org.sopt.and.data.local.TokenLocalDataSource +import org.sopt.and.data.remote.model.response.GetMyHobbyResponseDto +import org.sopt.and.di.ServicePool +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response import javax.inject.Inject @HiltViewModel -class MainViewModel @Inject constructor() : ViewModel() { +class MainViewModel @Inject constructor( + private val tokenLocalDataSource: TokenLocalDataSource +) : ViewModel() { + private val userService by lazy { ServicePool.userService } + private val _userHobbyState = MutableStateFlow(UserHobbyState.Idle) + val userHobbyState: StateFlow = _userHobbyState + fun getUserHobby() { + viewModelScope.launch { + userService.getMyHobby(token = tokenLocalDataSource.token).enqueue(object : + Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + _userHobbyState.value = + UserHobbyState.Success(response.body()?.result!!.hobby) + } else { + _userHobbyState.value = UserHobbyState.Failure(response.message()) + } + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + _userHobbyState.value = UserHobbyState.Failure(t.message.toString()) + } + } + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt index 431adb2..4ae120b 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/MyPageScreen.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -28,15 +27,11 @@ import androidx.compose.ui.unit.sp import org.sopt.and.R @Composable -fun MyPageScreen( - paddingValues: PaddingValues, - userEmail: String -) { +fun MyPageScreen(userHobby: String) { Column( modifier = Modifier .fillMaxWidth() .background(color = Color(0xFF161616)) - .padding(paddingValues) ) { Row( modifier = Modifier @@ -55,7 +50,7 @@ fun MyPageScreen( ) Spacer(modifier = Modifier.width(12.dp)) Text( - text = "${userEmail}님", + text = "${userHobby}님", color = Color.White ) Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt index bdcc9e6..ce42cae 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/SearchScreen.kt @@ -3,9 +3,7 @@ package org.sopt.and.presentation.ui.main.screen import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -13,14 +11,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @Composable -fun SearchScreen( - paddingValues: PaddingValues -) { +fun SearchScreen() { Column( modifier = Modifier .fillMaxSize() - .background(color = Color(0xFF161616)) - .padding(paddingValues), + .background(color = Color(0xFF161616)), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt new file mode 100644 index 0000000..fa33bab --- /dev/null +++ b/app/src/main/java/org/sopt/and/presentation/ui/main/screen/UserHobbyState.kt @@ -0,0 +1,7 @@ +package org.sopt.and.presentation.ui.main.screen + +sealed class UserHobbyState { + data object Idle: UserHobbyState() + data class Success(val hobby: String): UserHobbyState() + data class Failure(val errorMessage: String): UserHobbyState() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/navigation/KeyStorage.kt b/app/src/main/java/org/sopt/and/presentation/ui/navigation/KeyStorage.kt deleted file mode 100644 index d8f7567..0000000 --- a/app/src/main/java/org/sopt/and/presentation/ui/navigation/KeyStorage.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.sopt.and.presentation.ui.navigation - -object KeyStorage { - const val USER_EMAIL = "user_email" -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveNavHost.kt b/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveNavHost.kt index eb61c1b..508909f 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveNavHost.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveNavHost.kt @@ -4,21 +4,16 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import org.sopt.and.presentation.ui.auth.navigation.authNavGraph -import org.sopt.and.presentation.ui.auth.screen.AuthViewModel import org.sopt.and.presentation.ui.main.navigation.mainNavGraph -import org.sopt.and.presentation.ui.main.screen.MainViewModel @Composable fun WavveNavHost( navController: NavHostController, modifier: Modifier = Modifier, ) { - val authViewModel: AuthViewModel = hiltViewModel() - val mainViewModel: MainViewModel = hiltViewModel() Box( modifier = modifier.fillMaxSize() ) { @@ -26,8 +21,8 @@ fun WavveNavHost( navController = navController, startDestination = WavveRoute.AUTH, ) { - authNavGraph(navController, authViewModel) - mainNavGraph(navController, mainViewModel) + authNavGraph(navController) + mainNavGraph(navController) } } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveRoute.kt b/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveRoute.kt index 29da919..79572bf 100644 --- a/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveRoute.kt +++ b/app/src/main/java/org/sopt/and/presentation/ui/navigation/WavveRoute.kt @@ -7,5 +7,9 @@ object WavveRoute { const val SIGN_UP = "sign_up" // mainNavGraph + const val WAVVE = "wavve" const val MAIN = "main" + const val HOME = "home" + const val SEARCH = "search" + const val MY = "my" } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/utils/ToastUtils.kt b/app/src/main/java/org/sopt/and/presentation/util/ToastUtils.kt similarity index 88% rename from app/src/main/java/org/sopt/and/presentation/utils/ToastUtils.kt rename to app/src/main/java/org/sopt/and/presentation/util/ToastUtils.kt index 920f9ad..92b958d 100644 --- a/app/src/main/java/org/sopt/and/presentation/utils/ToastUtils.kt +++ b/app/src/main/java/org/sopt/and/presentation/util/ToastUtils.kt @@ -1,4 +1,4 @@ -package org.sopt.and.presentation.utils +package org.sopt.and.presentation.util import android.content.Context import android.widget.Toast diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f9a888..8942d43 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,13 @@ activityCompose = "1.8.0" composeBom = "2024.04.01" androidxComposeNavigation = "2.8.2" androidx-hilt-navigation-compose = "1.2.0" -kotlinxSerializationJson = "1.7.3" hilt = "2.51.1" appcompat = "1.7.0" +#Third Party +okhttp = "4.11.0" +retrofit = "2.9.0" +retrofitKotlinSerializationConverter = "1.0.0" +kotlinxSerializationJson = "1.7.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -30,15 +34,21 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation" } -kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidx-hilt-navigation-compose" } +# Third Party +okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } +okhttp = { group = "com.squareup.okhttp3", name = "okhttp" } +okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor" } +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-kotlin-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinSerializationConverter" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } \ No newline at end of file +hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file