diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a18a009..3b85493 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -5,6 +5,8 @@ plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.hilt.android)
+ id("kotlin-kapt")
}
val properties = Properties().apply {
@@ -46,9 +48,18 @@ android {
compose = true
buildConfig = true
}
+
+ hilt {
+ enableAggregatingTask = false
+ }
}
dependencies {
+ // 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)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7dcf9b7..b5a93b8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
diff --git a/app/src/main/java/org/sopt/and/WavveApp.kt b/app/src/main/java/org/sopt/and/WavveApp.kt
new file mode 100644
index 0000000..e16ed15
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/WavveApp.kt
@@ -0,0 +1,17 @@
+package org.sopt.and
+
+import android.app.Application
+import androidx.appcompat.app.AppCompatDelegate
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class WavveApp: Application() {
+ override fun onCreate() {
+ super.onCreate()
+ setDarkMode()
+ }
+
+ private fun setDarkMode() {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/ApiFactory.kt b/app/src/main/java/org/sopt/and/data/ApiFactory.kt
deleted file mode 100644
index 4ad4168..0000000
--- a/app/src/main/java/org/sopt/and/data/ApiFactory.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.sopt.and.data
-
-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.network.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 userService = ApiFactory.create()
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/remote/model/TodayTopData.kt b/app/src/main/java/org/sopt/and/data/local/TodayTopData.kt
similarity index 64%
rename from app/src/main/java/org/sopt/and/data/remote/model/TodayTopData.kt
rename to app/src/main/java/org/sopt/and/data/local/TodayTopData.kt
index 8ad0d84..dafa5c2 100644
--- a/app/src/main/java/org/sopt/and/data/remote/model/TodayTopData.kt
+++ b/app/src/main/java/org/sopt/and/data/local/TodayTopData.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.data.remote.model
+package org.sopt.and.data.local
data class TodayTopData(
val painterId: Int,
diff --git a/app/src/main/java/org/sopt/and/data/mapper/todata/UserLoginModelMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/todata/UserLoginModelMapper.kt
new file mode 100644
index 0000000..47e6be0
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/mapper/todata/UserLoginModelMapper.kt
@@ -0,0 +1,9 @@
+package org.sopt.and.data.mapper.todata
+
+import org.sopt.and.data.remote.model.request.UserLoginRequestDto
+import org.sopt.and.domain.model.request.UserLoginModel
+
+fun UserLoginModel.toData() : UserLoginRequestDto = UserLoginRequestDto(
+ username = this.username,
+ password = this.password
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/mapper/todata/UserSignUpModelMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/todata/UserSignUpModelMapper.kt
new file mode 100644
index 0000000..e5e58f9
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/mapper/todata/UserSignUpModelMapper.kt
@@ -0,0 +1,10 @@
+package org.sopt.and.data.mapper.todata
+
+import org.sopt.and.data.remote.model.request.UserSignUpRequestDto
+import org.sopt.and.domain.model.request.UserSignUpModel
+
+fun UserSignUpModel.toData() : UserSignUpRequestDto = UserSignUpRequestDto(
+ username = this.username,
+ password = this.password,
+ hobby = this.hobby
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/mapper/todomain/UserHobbyResponseDtoMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/todomain/UserHobbyResponseDtoMapper.kt
new file mode 100644
index 0000000..6311a29
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/mapper/todomain/UserHobbyResponseDtoMapper.kt
@@ -0,0 +1,8 @@
+package org.sopt.and.data.mapper.todomain
+
+import org.sopt.and.data.remote.model.response.UserHobbyResponseDto
+import org.sopt.and.domain.model.response.Hobby
+
+fun UserHobbyResponseDto.toDomain(): Hobby = Hobby(
+ hobby = this.hobby
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/mapper/todomain/UserSignUpResponseDtoMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/todomain/UserSignUpResponseDtoMapper.kt
new file mode 100644
index 0000000..5ed6022
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/mapper/todomain/UserSignUpResponseDtoMapper.kt
@@ -0,0 +1,8 @@
+package org.sopt.and.data.mapper.todomain
+
+import org.sopt.and.data.remote.model.response.UserSignUpResponseDto
+import org.sopt.and.domain.model.response.UserNumber
+
+fun UserSignUpResponseDto.toDomain(): UserNumber = UserNumber(
+ userId = this.userId
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/mapper/todomain/UserTokenResponseDtoMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/todomain/UserTokenResponseDtoMapper.kt
new file mode 100644
index 0000000..51df841
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/mapper/todomain/UserTokenResponseDtoMapper.kt
@@ -0,0 +1,8 @@
+package org.sopt.and.data.mapper.todomain
+
+import org.sopt.and.data.remote.model.response.UserTokenResponseDto
+import org.sopt.and.domain.model.response.Token
+
+fun UserTokenResponseDto.toDomain(): Token = Token(
+ token = this.token
+)
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/remote/datasource/UserDataRemoteSource.kt b/app/src/main/java/org/sopt/and/data/remote/datasource/UserDataRemoteSource.kt
new file mode 100644
index 0000000..6d49afc
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/remote/datasource/UserDataRemoteSource.kt
@@ -0,0 +1,14 @@
+package org.sopt.and.data.remote.datasource
+
+import org.sopt.and.data.remote.model.base.ApiResponse
+import org.sopt.and.data.remote.model.request.UserLoginRequestDto
+import org.sopt.and.data.remote.model.request.UserSignUpRequestDto
+import org.sopt.and.data.remote.model.response.UserHobbyResponseDto
+import org.sopt.and.data.remote.model.response.UserSignUpResponseDto
+import org.sopt.and.data.remote.model.response.UserTokenResponseDto
+
+interface UserDataRemoteSource {
+ suspend fun postUserSignUp(userSignUpRequestDto: UserSignUpRequestDto): ApiResponse
+ suspend fun postUserLogin(userLoginRequestDto: UserLoginRequestDto): ApiResponse
+ suspend fun getUserHobby(token: String): ApiResponse
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/remote/datasourceimpl/UserDataRemoteSourceImpl.kt b/app/src/main/java/org/sopt/and/data/remote/datasourceimpl/UserDataRemoteSourceImpl.kt
new file mode 100644
index 0000000..9415dd2
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/remote/datasourceimpl/UserDataRemoteSourceImpl.kt
@@ -0,0 +1,26 @@
+package org.sopt.and.data.remote.datasourceimpl
+
+import org.sopt.and.data.remote.datasource.UserDataRemoteSource
+import org.sopt.and.data.remote.model.base.ApiResponse
+import org.sopt.and.data.remote.model.request.UserLoginRequestDto
+import org.sopt.and.data.remote.model.request.UserSignUpRequestDto
+import org.sopt.and.data.remote.model.response.UserHobbyResponseDto
+import org.sopt.and.data.remote.model.response.UserSignUpResponseDto
+import org.sopt.and.data.remote.model.response.UserTokenResponseDto
+import org.sopt.and.data.remote.service.UserService
+import javax.inject.Inject
+
+class UserDataRemoteSourceImpl @Inject constructor(
+ private val userService: UserService
+) : UserDataRemoteSource {
+
+ override suspend fun postUserSignUp(userSignUpRequestDto: UserSignUpRequestDto): ApiResponse =
+ userService.postUserSignUp(body = userSignUpRequestDto)
+
+ override suspend fun postUserLogin(userLoginRequestDto: UserLoginRequestDto): ApiResponse =
+ userService.postUserLogin(body = userLoginRequestDto)
+
+
+ override suspend fun getUserHobby(token: String): ApiResponse =
+ userService.getUserHobby(token = token)
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/remote/model/response/UserSignUpResponseDto.kt b/app/src/main/java/org/sopt/and/data/remote/model/response/UserSignUpResponseDto.kt
index 6609fd9..42aa942 100644
--- a/app/src/main/java/org/sopt/and/data/remote/model/response/UserSignUpResponseDto.kt
+++ b/app/src/main/java/org/sopt/and/data/remote/model/response/UserSignUpResponseDto.kt
@@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class UserSignUpResponseDto(
@SerialName("no")
- val no: Int
+ val userId: Int
)
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/network/service/UserService.kt b/app/src/main/java/org/sopt/and/data/remote/service/UserService.kt
similarity index 95%
rename from app/src/main/java/org/sopt/and/data/network/service/UserService.kt
rename to app/src/main/java/org/sopt/and/data/remote/service/UserService.kt
index 7f5013a..18cbf9b 100644
--- a/app/src/main/java/org/sopt/and/data/network/service/UserService.kt
+++ b/app/src/main/java/org/sopt/and/data/remote/service/UserService.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.data.network.service
+package org.sopt.and.data.remote.service
import org.sopt.and.data.remote.model.base.ApiResponse
import org.sopt.and.data.remote.model.request.UserLoginRequestDto
diff --git a/app/src/main/java/org/sopt/and/data/remote/utils/ApiResponseHandler.kt b/app/src/main/java/org/sopt/and/data/remote/utils/ApiResponseHandler.kt
new file mode 100644
index 0000000..f6f19d3
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/remote/utils/ApiResponseHandler.kt
@@ -0,0 +1,11 @@
+package org.sopt.and.data.remote.utils
+
+import org.sopt.and.data.remote.model.base.ApiResponse
+
+fun ApiResponse.handleApiResponse(): Result {
+ return try {
+ Result.success(this.result)
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/UserRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/UserRepositoryImpl.kt
new file mode 100644
index 0000000..989aeb2
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/UserRepositoryImpl.kt
@@ -0,0 +1,36 @@
+package org.sopt.and.data.repositoryimpl
+
+import org.sopt.and.data.mapper.todata.toData
+import org.sopt.and.data.mapper.todomain.toDomain
+import org.sopt.and.data.remote.datasource.UserDataRemoteSource
+import org.sopt.and.data.remote.utils.handleApiResponse
+import org.sopt.and.domain.model.request.UserLoginModel
+import org.sopt.and.domain.model.request.UserSignUpModel
+import org.sopt.and.domain.model.response.Hobby
+import org.sopt.and.domain.model.response.Token
+import org.sopt.and.domain.model.response.UserNumber
+import org.sopt.and.domain.repository.UserRepository
+import javax.inject.Inject
+
+class UserRepositoryImpl @Inject constructor(
+ private val userDataRemoteSource: UserDataRemoteSource
+) : UserRepository {
+
+ override suspend fun postUserSignUp(userSignUpModel: UserSignUpModel): Result {
+ return runCatching {
+ userDataRemoteSource.postUserSignUp(userSignUpModel.toData()).handleApiResponse().getOrThrow().toDomain()
+ }
+ }
+
+ override suspend fun postUserLogin(userLoginModel: UserLoginModel): Result {
+ return runCatching {
+ userDataRemoteSource.postUserLogin(userLoginModel.toData()).handleApiResponse().getOrThrow().toDomain()
+ }
+ }
+
+ override suspend fun getUserHobby(token: String): Result {
+ return runCatching {
+ userDataRemoteSource.getUserHobby(token = token).handleApiResponse().getOrThrow().toDomain()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/di/DataSourceModule.kt b/app/src/main/java/org/sopt/and/di/DataSourceModule.kt
new file mode 100644
index 0000000..ed50f7f
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/di/DataSourceModule.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.data.remote.datasource.UserDataRemoteSource
+import org.sopt.and.data.remote.datasourceimpl.UserDataRemoteSourceImpl
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class DataSourceModule {
+ @Binds
+ @Singleton
+ abstract fun bindUserDataRemoteSource(
+ userDataRemoteSourceImpl: UserDataRemoteSourceImpl
+ ): UserDataRemoteSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/di/NetworkModule.kt b/app/src/main/java/org/sopt/and/di/NetworkModule.kt
new file mode 100644
index 0000000..22ed31f
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/di/NetworkModule.kt
@@ -0,0 +1,72 @@
+package org.sopt.and.di
+
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import org.sopt.and.BuildConfig
+import retrofit2.Retrofit
+import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
+
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+ @Provides
+ @Singleton
+ fun providesOkHttpClient(
+ loggingInterceptor: HttpLoggingInterceptor,
+ authInterceptor: Interceptor
+ ): OkHttpClient =
+ OkHttpClient.Builder().apply {
+ connectTimeout(10, TimeUnit.SECONDS)
+ writeTimeout(10, TimeUnit.SECONDS)
+ readTimeout(10, TimeUnit.SECONDS)
+ addInterceptor(authInterceptor)
+ }.build()
+
+ @Provides
+ @Singleton
+ fun providesJson(): Json =
+ Json {
+ isLenient = true
+ prettyPrint = true
+ explicitNulls = false
+ ignoreUnknownKeys = true
+ }
+
+ @Provides
+ @Singleton
+ fun provideLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+
+ @Provides
+ @Singleton
+ fun provideAuthInterceptor(): Interceptor = Interceptor { chain ->
+ val request = chain.request().newBuilder()
+ .addHeader("Authorization", "Bearer ")
+ .build()
+ chain.proceed(request)
+ }
+
+ @Provides
+ @Singleton
+ fun provideWavveRetrofit(
+ okHttpClient: OkHttpClient,
+ json: Json
+ ): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl(BuildConfig.BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
+ .build()
+ }
+}
\ 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..398f170
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/di/RepositoryModule.kt
@@ -0,0 +1,20 @@
+package org.sopt.and.di
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import org.sopt.and.data.repositoryimpl.UserRepositoryImpl
+import org.sopt.and.domain.repository.UserRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class RepositoryModule {
+ @Binds
+ @Singleton
+ abstract fun bindUserRepository(
+ userRepositoryImpl: UserRepositoryImpl
+ ): UserRepository
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/di/ServiceModule.kt b/app/src/main/java/org/sopt/and/di/ServiceModule.kt
new file mode 100644
index 0000000..6afb689
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/di/ServiceModule.kt
@@ -0,0 +1,18 @@
+package org.sopt.and.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import org.sopt.and.data.remote.service.UserService
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ServiceModule {
+ @Provides
+ @Singleton
+ fun provideUserService(retrofit: Retrofit): UserService =
+ retrofit.create(UserService::class.java)
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/domain/model/request/UserLoginModel.kt b/app/src/main/java/org/sopt/and/domain/model/request/UserLoginModel.kt
new file mode 100644
index 0000000..fe142ba
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/model/request/UserLoginModel.kt
@@ -0,0 +1,6 @@
+package org.sopt.and.domain.model.request
+
+data class UserLoginModel(
+ val username: String,
+ val password: String
+)
diff --git a/app/src/main/java/org/sopt/and/domain/model/request/UserSignUpModel.kt b/app/src/main/java/org/sopt/and/domain/model/request/UserSignUpModel.kt
new file mode 100644
index 0000000..e725d98
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/model/request/UserSignUpModel.kt
@@ -0,0 +1,7 @@
+package org.sopt.and.domain.model.request
+
+data class UserSignUpModel(
+ val username: String,
+ val password: String,
+ val hobby: String
+)
diff --git a/app/src/main/java/org/sopt/and/domain/model/response/Hobby.kt b/app/src/main/java/org/sopt/and/domain/model/response/Hobby.kt
new file mode 100644
index 0000000..a9972da
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/model/response/Hobby.kt
@@ -0,0 +1,5 @@
+package org.sopt.and.domain.model.response
+
+data class Hobby(
+ val hobby: String
+)
diff --git a/app/src/main/java/org/sopt/and/domain/model/response/Token.kt b/app/src/main/java/org/sopt/and/domain/model/response/Token.kt
new file mode 100644
index 0000000..46abcc2
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/model/response/Token.kt
@@ -0,0 +1,5 @@
+package org.sopt.and.domain.model.response
+
+data class Token(
+ val token: String
+)
diff --git a/app/src/main/java/org/sopt/and/domain/model/response/UserNumber.kt b/app/src/main/java/org/sopt/and/domain/model/response/UserNumber.kt
new file mode 100644
index 0000000..c951271
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/model/response/UserNumber.kt
@@ -0,0 +1,5 @@
+package org.sopt.and.domain.model.response
+
+data class UserNumber(
+ val userId: Int
+)
diff --git a/app/src/main/java/org/sopt/and/domain/repository/UserRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/UserRepository.kt
new file mode 100644
index 0000000..d18fd2e
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/repository/UserRepository.kt
@@ -0,0 +1,13 @@
+package org.sopt.and.domain.repository
+
+import org.sopt.and.domain.model.request.UserLoginModel
+import org.sopt.and.domain.model.request.UserSignUpModel
+import org.sopt.and.domain.model.response.Hobby
+import org.sopt.and.domain.model.response.Token
+import org.sopt.and.domain.model.response.UserNumber
+
+interface UserRepository {
+ suspend fun postUserSignUp(userSignUpModel: UserSignUpModel): Result
+ suspend fun postUserLogin(userLoginModel: UserLoginModel): Result
+ suspend fun getUserHobby(token: String): Result
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/domain/type/HomeTabType.kt b/app/src/main/java/org/sopt/and/domain/type/HomeTabType.kt
new file mode 100644
index 0000000..29a92c0
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/domain/type/HomeTabType.kt
@@ -0,0 +1,24 @@
+package org.sopt.and.domain.type
+
+enum class HomeTabType(
+ val text: String,
+) {
+ NEW_CLASSIC(
+ text = "뉴클래식"
+ ),
+ DRAMA(
+ text = "드라마"
+ ),
+ VARIETY_SHOW(
+ text = "예능"
+ ),
+ MOVIE(
+ text = "영화"
+ ),
+ ANIMATION(
+ text = "애니"
+ ),
+ OVERSEAS_SERIES(
+ text = "해외시리즈"
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/home/HomeViewModel.kt b/app/src/main/java/org/sopt/and/feature/home/HomeViewModel.kt
deleted file mode 100644
index caa788f..0000000
--- a/app/src/main/java/org/sopt/and/feature/home/HomeViewModel.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.sopt.and.feature.home
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.ViewModel
-import org.sopt.and.R
-import org.sopt.and.data.remote.model.TodayTopData
-
-class HomeViewModel : ViewModel() {
- private val _homeTabText = listOf(
- R.string.new_classic,
- R.string.drama,
- R.string.variety_show,
- R.string.movie,
- R.string.animation,
- R.string.overseas_series
- )
-
- val homeTabText: List
- @Composable
- get() = _homeTabText.map { stringResource(it) }
-
- val editorDummy = List(20) {
- TodayTopData(
- painterId = R.drawable.iv_editor_recommended_work,
- ranking = it + 1
- )
- }
-
- val top20Dummy = List(20) {
- TodayTopData(
- painterId = R.drawable.iv_today_top_20,
- ranking = it + 1
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/login/LoginViewModel.kt b/app/src/main/java/org/sopt/and/feature/login/LoginViewModel.kt
deleted file mode 100644
index a6b3f4f..0000000
--- a/app/src/main/java/org/sopt/and/feature/login/LoginViewModel.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.sopt.and.feature.login
-
-import android.content.Context
-import android.util.Log
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.launch
-import okio.IOException
-import org.sopt.and.R
-import org.sopt.and.data.ServicePool
-import org.sopt.and.data.remote.model.request.UserLoginRequestDto
-import org.sopt.and.utils.toast
-import retrofit2.HttpException
-
-class LoginViewModel : ViewModel() {
- private val userService by lazy { ServicePool.userService }
-
- private val _token = MutableLiveData(null)
- val token: LiveData get() = _token
-
- fun postUserLogin(
- context: Context,
- body: UserLoginRequestDto,
- ) {
- viewModelScope.launch {
- runCatching {
- userService.postUserLogin(body = body)
- }.onSuccess { response ->
- _token.value = response.result.token
- }.onFailure { error ->
- when (error) {
- is HttpException -> {
- when (error.code()) {
- 400 -> context.toast(context.getString(R.string.fail_to_login))
- 403 -> context.toast(context.getString(R.string.fail_to_login_invalid_password))
- }
- }
-
- is IOException -> {
- context.toast(context.getString(R.string.fail_to_network))
- }
-
- else -> {
- context.toast(context.getString(R.string.fail_to_login))
- }
- }
- Log.e("postUserLoginError", error.toString())
- }
- }
- }
-
- private val _userName = MutableLiveData("")
- val userName: LiveData get() = _userName
-
- private val _password = MutableLiveData("")
- val password: LiveData get() = _password
-
-
- fun setUserName(newName: String) {
- _userName.value = newName
- }
-
- fun setPassword(newPassword: String) {
- _password.value = newPassword
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/mypage/MyViewModel.kt b/app/src/main/java/org/sopt/and/feature/mypage/MyViewModel.kt
deleted file mode 100644
index d9f1ea5..0000000
--- a/app/src/main/java/org/sopt/and/feature/mypage/MyViewModel.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.sopt.and.feature.mypage
-
-import android.util.Log
-import androidx.compose.runtime.State
-import androidx.compose.runtime.mutableStateOf
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.launch
-import org.sopt.and.data.ServicePool
-import org.sopt.and.data.remote.model.response.UserHobbyResponseDto
-
-class MyViewModel : ViewModel() {
- private val userService by lazy { ServicePool.userService }
-
- private val _userState = mutableStateOf(null)
- val userState: State get() = _userState
-
- private val _hobby = MutableLiveData("")
- val hobby: LiveData get() = _hobby
-
- fun getUserHobby(token: String) {
- viewModelScope.launch {
- runCatching {
- userService.getUserHobby(token = token)
- }.onSuccess { response ->
- _userState.value = response.result
- _hobby.value = response.result.hobby
- }.onFailure { error ->
- Log.e("getUserHobbyError", error.toString())
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/search/SearchViewModel.kt b/app/src/main/java/org/sopt/and/feature/search/SearchViewModel.kt
deleted file mode 100644
index f3cb802..0000000
--- a/app/src/main/java/org/sopt/and/feature/search/SearchViewModel.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.sopt.and.feature.search
-
-import androidx.lifecycle.ViewModel
-
-class SearchViewModel : ViewModel() {
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/Banner.kt b/app/src/main/java/org/sopt/and/presentation/core/component/Banner.kt
similarity index 98%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/Banner.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/Banner.kt
index 7bac48d..d123010 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/Banner.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/Banner.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomBox.kt b/app/src/main/java/org/sopt/and/presentation/core/component/CustomBox.kt
similarity index 98%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/CustomBox.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/CustomBox.kt
index 27c81ee..297d41e 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomBox.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/CustomBox.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.sopt.and.R
-import org.sopt.and.data.remote.model.TodayTopData
+import org.sopt.and.data.local.TodayTopData
import org.sopt.and.ui.theme.WavveTheme
@Composable
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomButton.kt b/app/src/main/java/org/sopt/and/presentation/core/component/CustomButton.kt
similarity index 98%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/CustomButton.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/CustomButton.kt
index ddb9d7f..8a5d964 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomButton.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/CustomButton.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomDialog.kt b/app/src/main/java/org/sopt/and/presentation/core/component/CustomDialog.kt
similarity index 98%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/CustomDialog.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/CustomDialog.kt
index e1337ca..47eb3a0 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomDialog.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/CustomDialog.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomTabBar.kt b/app/src/main/java/org/sopt/and/presentation/core/component/CustomTabBar.kt
similarity index 86%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/CustomTabBar.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/CustomTabBar.kt
index c71ae50..c7700c6 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomTabBar.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/CustomTabBar.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
@@ -12,12 +12,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import org.sopt.and.domain.type.HomeTabType
import org.sopt.and.ui.theme.WavveTheme
@Composable
fun WavveTabBar(
- tabTitles: List, modifier: Modifier = Modifier
+ tabTitles: Array, modifier: Modifier = Modifier
) {
var selectedTabIndex by remember { mutableIntStateOf(0) }
@@ -35,7 +36,7 @@ fun WavveTabBar(
onClick = { selectedTabIndex = index },
text = {
Text(
- text = tab,
+ text = tab.text,
fontSize = 18.sp,
color = if (selectedTabIndex == index) Color.White else Color.Gray
)
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomTextField.kt b/app/src/main/java/org/sopt/and/presentation/core/component/CustomTextField.kt
similarity index 97%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/CustomTextField.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/CustomTextField.kt
index a97cfd6..6cb7c28 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/CustomTextField.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/CustomTextField.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
diff --git a/app/src/main/java/org/sopt/and/core/designsystem/component/SocialLoginButton.kt b/app/src/main/java/org/sopt/and/presentation/core/component/SocialLoginButton.kt
similarity index 98%
rename from app/src/main/java/org/sopt/and/core/designsystem/component/SocialLoginButton.kt
rename to app/src/main/java/org/sopt/and/presentation/core/component/SocialLoginButton.kt
index 5be59c4..14a178b 100644
--- a/app/src/main/java/org/sopt/and/core/designsystem/component/SocialLoginButton.kt
+++ b/app/src/main/java/org/sopt/and/presentation/core/component/SocialLoginButton.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.core.designsystem.component
+package org.sopt.and.presentation.core.component
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
diff --git a/app/src/main/java/org/sopt/and/feature/home/HomeScreen.kt b/app/src/main/java/org/sopt/and/presentation/home/HomeScreen.kt
similarity index 93%
rename from app/src/main/java/org/sopt/and/feature/home/HomeScreen.kt
rename to app/src/main/java/org/sopt/and/presentation/home/HomeScreen.kt
index bf4962d..7ae8d31 100644
--- a/app/src/main/java/org/sopt/and/feature/home/HomeScreen.kt
+++ b/app/src/main/java/org/sopt/and/presentation/home/HomeScreen.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.home
+package org.sopt.and.presentation.home
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -36,18 +36,18 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import org.sopt.and.R
-import org.sopt.and.core.designsystem.component.Banner
-import org.sopt.and.core.designsystem.component.EditorRecommendBox
-import org.sopt.and.core.designsystem.component.TodayTop20Box
-import org.sopt.and.core.designsystem.component.WavveTabBar
+import org.sopt.and.presentation.core.component.Banner
+import org.sopt.and.presentation.core.component.EditorRecommendBox
+import org.sopt.and.presentation.core.component.TodayTop20Box
+import org.sopt.and.presentation.core.component.WavveTabBar
import org.sopt.and.ui.theme.WavveTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun HomeScreen(navController: NavController, viewModel: HomeViewModel = viewModel()) {
+fun HomeScreen(navController: NavController, viewModel: HomeViewModel = hiltViewModel()) {
val scrollState = rememberScrollState()
val pagerState = rememberPagerState(
pageCount = { 6 }
@@ -93,7 +93,7 @@ fun HomeScreen(navController: NavController, viewModel: HomeViewModel = viewMode
.fillMaxSize()
.verticalScroll(scrollState)
) {
- WavveTabBar(viewModel.homeTabText)
+ WavveTabBar(tabTitles = viewModel.homeTabText)
HorizontalPager(
modifier = Modifier
diff --git a/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt b/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt
new file mode 100644
index 0000000..9d8f829
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt
@@ -0,0 +1,28 @@
+package org.sopt.and.presentation.home
+
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import org.sopt.and.R
+import org.sopt.and.data.local.TodayTopData
+import org.sopt.and.domain.type.HomeTabType
+import javax.inject.Inject
+
+@HiltViewModel
+class HomeViewModel @Inject constructor(
+) : ViewModel() {
+ val homeTabText = HomeTabType.entries.toTypedArray()
+
+ val editorDummy = List(20) {
+ TodayTopData(
+ painterId = R.drawable.iv_editor_recommended_work,
+ ranking = it + 1
+ )
+ }
+
+ val top20Dummy = List(20) {
+ TodayTopData(
+ painterId = R.drawable.iv_today_top_20,
+ ranking = it + 1
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt b/app/src/main/java/org/sopt/and/presentation/login/LoginScreen.kt
similarity index 90%
rename from app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt
rename to app/src/main/java/org/sopt/and/presentation/login/LoginScreen.kt
index bd3a0b3..05e4b23 100644
--- a/app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt
+++ b/app/src/main/java/org/sopt/and/presentation/login/LoginScreen.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.login
+package org.sopt.and.presentation.login
import android.content.Context
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
@@ -29,6 +29,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -47,14 +48,14 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import org.sopt.and.R
-import org.sopt.and.core.designsystem.component.AuthTextField
-import org.sopt.and.core.designsystem.component.SocialLoginButtonGroup
-import org.sopt.and.core.designsystem.component.WavveLoginButton
-import org.sopt.and.data.remote.model.request.UserLoginRequestDto
-import org.sopt.and.feature.main.Routes
+import org.sopt.and.domain.model.request.UserLoginModel
+import org.sopt.and.presentation.core.component.AuthTextField
+import org.sopt.and.presentation.core.component.SocialLoginButtonGroup
+import org.sopt.and.presentation.core.component.WavveLoginButton
+import org.sopt.and.presentation.main.Routes
import org.sopt.and.ui.theme.WavveTheme
import org.sopt.and.utils.noRippleClickable
@@ -63,7 +64,7 @@ import org.sopt.and.utils.noRippleClickable
fun LoginScreen(
navController: NavController,
onLoginSuccess: (String, String) -> Unit,
- viewModel: LoginViewModel = viewModel()
+ viewModel: LoginViewModel = hiltViewModel()
) {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
@@ -72,6 +73,7 @@ fun LoginScreen(
val editor = sharedPreferences.edit()
+ val loginState by viewModel.loginState.collectAsState()
val token by viewModel.token.observeAsState("")
val userName by viewModel.userName.observeAsState("")
val password by viewModel.password.observeAsState("")
@@ -178,17 +180,18 @@ fun LoginScreen(
onClick = {
viewModel.postUserLogin(
context = context,
- body = UserLoginRequestDto(
+ body = UserLoginModel(
username = userName,
- password = password
+ password = password,
),
+ onSuccess = {
+ editor.putString(context.getString(R.string.login_token), token)
+ editor.apply()
+ onLoginSuccess(userName, password)
+ //키보드 내리기
+ focusManager.clearFocus()
+ }
)
-
- editor.putString(context.getString(R.string.login_token), token)
- editor.apply()
- onLoginSuccess(userName, password)
- //키보드 내리기
- focusManager.clearFocus()
}
)
diff --git a/app/src/main/java/org/sopt/and/presentation/login/LoginState.kt b/app/src/main/java/org/sopt/and/presentation/login/LoginState.kt
new file mode 100644
index 0000000..e1b246f
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/login/LoginState.kt
@@ -0,0 +1,10 @@
+package org.sopt.and.presentation.login
+
+import org.sopt.and.domain.model.response.Token
+
+sealed class LoginState {
+ data object Idle: LoginState()
+ data object Loading: LoginState()
+ data class Success(val result: Token): LoginState()
+ data class Failure(val message: String): LoginState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/presentation/login/LoginViewModel.kt b/app/src/main/java/org/sopt/and/presentation/login/LoginViewModel.kt
new file mode 100644
index 0000000..0d55dd9
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/login/LoginViewModel.kt
@@ -0,0 +1,71 @@
+package org.sopt.and.presentation.login
+
+import android.content.Context
+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 kotlinx.coroutines.launch
+import org.sopt.and.R
+import org.sopt.and.domain.model.request.UserLoginModel
+import org.sopt.and.domain.repository.UserRepository
+import org.sopt.and.utils.handleErrorToast
+import javax.inject.Inject
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ private val userRepository: UserRepository
+) : ViewModel() {
+ private val _loginState = MutableStateFlow(LoginState.Idle)
+ val loginState: StateFlow = _loginState
+
+ private val _token = MutableLiveData(null)
+ val token: LiveData get() = _token
+
+ fun postUserLogin(
+ context: Context,
+ body: UserLoginModel,
+ onSuccess: () -> Unit
+ ) {
+ _loginState.value = LoginState.Loading
+ viewModelScope.launch {
+ val result = userRepository.postUserLogin(
+ userLoginModel = body
+ )
+ _loginState.value = result.fold(
+ onSuccess = { response ->
+ _token.value = response.token
+ onSuccess()
+ LoginState.Success(response)
+ },
+ onFailure = { error ->
+ handleErrorToast(
+ exception = error,
+ is400Error = R.string.fail_to_login,
+ is403Error = R.string.fail_to_login_invalid_password,
+ context = context
+ )
+ LoginState.Failure(error.message.orEmpty())
+ }
+ )
+ }
+ }
+
+ private val _userName = MutableLiveData("")
+ val userName: LiveData get() = _userName
+
+ private val _password = MutableLiveData("")
+ val password: LiveData get() = _password
+
+
+ fun setUserName(newName: String) {
+ _userName.value = newName
+ }
+
+ fun setPassword(newPassword: String) {
+ _password.value = newPassword
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/main/BottomNavigationBar.kt b/app/src/main/java/org/sopt/and/presentation/main/BottomNavigationBar.kt
similarity index 98%
rename from app/src/main/java/org/sopt/and/feature/main/BottomNavigationBar.kt
rename to app/src/main/java/org/sopt/and/presentation/main/BottomNavigationBar.kt
index 1d4c79a..e21c435 100644
--- a/app/src/main/java/org/sopt/and/feature/main/BottomNavigationBar.kt
+++ b/app/src/main/java/org/sopt/and/presentation/main/BottomNavigationBar.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.main
+package org.sopt.and.presentation.main
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.height
diff --git a/app/src/main/java/org/sopt/and/feature/main/MainActivity.kt b/app/src/main/java/org/sopt/and/presentation/main/MainActivity.kt
similarity index 93%
rename from app/src/main/java/org/sopt/and/feature/main/MainActivity.kt
rename to app/src/main/java/org/sopt/and/presentation/main/MainActivity.kt
index a600584..cf1b66e 100644
--- a/app/src/main/java/org/sopt/and/feature/main/MainActivity.kt
+++ b/app/src/main/java/org/sopt/and/presentation/main/MainActivity.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.main
+package org.sopt.and.presentation.main
import android.annotation.SuppressLint
import android.os.Bundle
@@ -9,9 +9,11 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
+import dagger.hilt.android.AndroidEntryPoint
import org.sopt.and.ui.theme.ANDANDROIDTheme
import org.sopt.and.utils.currentRoute
+@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
diff --git a/app/src/main/java/org/sopt/and/feature/main/NavGraph.kt b/app/src/main/java/org/sopt/and/presentation/main/NavGraph.kt
similarity index 71%
rename from app/src/main/java/org/sopt/and/feature/main/NavGraph.kt
rename to app/src/main/java/org/sopt/and/presentation/main/NavGraph.kt
index a2ae220..dcafde9 100644
--- a/app/src/main/java/org/sopt/and/feature/main/NavGraph.kt
+++ b/app/src/main/java/org/sopt/and/presentation/main/NavGraph.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.main
+package org.sopt.and.presentation.main
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -12,16 +12,11 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import org.sopt.and.R
-import org.sopt.and.feature.home.HomeScreen
-import org.sopt.and.feature.home.HomeViewModel
-import org.sopt.and.feature.login.LoginScreen
-import org.sopt.and.feature.login.LoginViewModel
-import org.sopt.and.feature.mypage.MyScreen
-import org.sopt.and.feature.mypage.MyViewModel
-import org.sopt.and.feature.search.SearchScreen
-import org.sopt.and.feature.search.SearchViewModel
-import org.sopt.and.feature.signup.SignUpScreen
-import org.sopt.and.feature.signup.SignUpViewModel
+import org.sopt.and.presentation.home.HomeScreen
+import org.sopt.and.presentation.login.LoginScreen
+import org.sopt.and.presentation.mypage.MyScreen
+import org.sopt.and.presentation.search.SearchScreen
+import org.sopt.and.presentation.signup.SignUpScreen
import org.sopt.and.utils.toast
@Composable
@@ -54,7 +49,6 @@ fun NavGraph(
popUpTo(Routes.Home.screen) { inclusive = true }
}
},
- viewModel = LoginViewModel()
)
}
composable(Routes.SignUp.screen) {
@@ -65,20 +59,18 @@ fun NavGraph(
popUpTo(Routes.Login.screen) { inclusive = true }
}
},
- viewModel = SignUpViewModel()
)
}
composable(Routes.Home.screen) {
isLoggedIn = true
- HomeScreen(navController = navController, viewModel = HomeViewModel())
+ HomeScreen(navController = navController)
}
composable(Routes.Search.screen) {
- SearchScreen(navController = navController, viewModel = SearchViewModel())
+ SearchScreen(navController = navController)
}
composable(Routes.My.screen) {
MyScreen(
navController = navController,
- viewModel = MyViewModel()
)
}
}
diff --git a/app/src/main/java/org/sopt/and/feature/main/Routes.kt b/app/src/main/java/org/sopt/and/presentation/main/Routes.kt
similarity index 84%
rename from app/src/main/java/org/sopt/and/feature/main/Routes.kt
rename to app/src/main/java/org/sopt/and/presentation/main/Routes.kt
index d235d93..ee8123e 100644
--- a/app/src/main/java/org/sopt/and/feature/main/Routes.kt
+++ b/app/src/main/java/org/sopt/and/presentation/main/Routes.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.main
+package org.sopt.and.presentation.main
sealed class Routes (val screen: String){
object SignUp: Routes("signup")
diff --git a/app/src/main/java/org/sopt/and/feature/mypage/MyScreen.kt b/app/src/main/java/org/sopt/and/presentation/mypage/MyScreen.kt
similarity index 83%
rename from app/src/main/java/org/sopt/and/feature/mypage/MyScreen.kt
rename to app/src/main/java/org/sopt/and/presentation/mypage/MyScreen.kt
index 2b30da0..1d7ba00 100644
--- a/app/src/main/java/org/sopt/and/feature/mypage/MyScreen.kt
+++ b/app/src/main/java/org/sopt/and/presentation/mypage/MyScreen.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.mypage
+package org.sopt.and.presentation.mypage
import android.content.Context
import androidx.compose.foundation.background
@@ -10,30 +10,33 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import org.sopt.and.R
-import org.sopt.and.core.designsystem.component.EmptyBox
-import org.sopt.and.core.designsystem.component.ProfileBox
-import org.sopt.and.core.designsystem.component.TicketBox
+import org.sopt.and.presentation.core.component.EmptyBox
+import org.sopt.and.presentation.core.component.ProfileBox
+import org.sopt.and.presentation.core.component.TicketBox
import org.sopt.and.ui.theme.WavveTheme
@Composable
fun MyScreen(
navController: NavController,
- viewModel: MyViewModel = viewModel()
+ viewModel: MyViewModel = hiltViewModel()
) {
val context = LocalContext.current
val sharedPreferences = context.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPreferences.getString("loginToken", "").orEmpty()
- viewModel.getUserHobby(token = token)
+ LaunchedEffect(Unit) {
+ viewModel.getUserHobby(token = token)
+ }
val hobby by viewModel.hobby.observeAsState("")
diff --git a/app/src/main/java/org/sopt/and/presentation/mypage/MyState.kt b/app/src/main/java/org/sopt/and/presentation/mypage/MyState.kt
new file mode 100644
index 0000000..03dba23
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/mypage/MyState.kt
@@ -0,0 +1,10 @@
+package org.sopt.and.presentation.mypage
+
+import org.sopt.and.domain.model.response.Hobby
+
+sealed class MyState {
+ data object Idle : MyState()
+ data object Loading : MyState()
+ data class Success(val result: Hobby) : MyState()
+ data class Failure(val message: String) : MyState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/presentation/mypage/MyViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypage/MyViewModel.kt
new file mode 100644
index 0000000..86d36ee
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/mypage/MyViewModel.kt
@@ -0,0 +1,40 @@
+package org.sopt.and.presentation.mypage
+
+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 kotlinx.coroutines.launch
+import org.sopt.and.domain.repository.UserRepository
+import javax.inject.Inject
+
+@HiltViewModel
+class MyViewModel @Inject constructor(
+ private val userRepository: UserRepository
+) : ViewModel() {
+ private val _myState = MutableStateFlow(MyState.Idle)
+ val myState: StateFlow get() = _myState
+
+ private val _hobby = MutableLiveData("")
+ val hobby: LiveData get() = _hobby
+
+ fun getUserHobby(token: String) {
+ _myState.value = MyState.Loading
+ viewModelScope.launch {
+ val result = userRepository.getUserHobby(token = token)
+ _myState.value = result.fold(
+ onSuccess = { response ->
+ _hobby.value = response.hobby
+ MyState.Success(response)
+ },
+ onFailure = { error ->
+ MyState.Failure(error.message.toString())
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/search/SearchScreen.kt b/app/src/main/java/org/sopt/and/presentation/search/SearchScreen.kt
similarity index 88%
rename from app/src/main/java/org/sopt/and/feature/search/SearchScreen.kt
rename to app/src/main/java/org/sopt/and/presentation/search/SearchScreen.kt
index 17179b5..29ceea7 100644
--- a/app/src/main/java/org/sopt/and/feature/search/SearchScreen.kt
+++ b/app/src/main/java/org/sopt/and/presentation/search/SearchScreen.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.search
+package org.sopt.and.presentation.search
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -9,12 +9,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import org.sopt.and.ui.theme.WavveTheme
@Composable
-fun SearchScreen(navController: NavController, viewModel: SearchViewModel = viewModel()) {
+fun SearchScreen(navController: NavController, viewModel: SearchViewModel = hiltViewModel()) {
Scaffold(
modifier = Modifier.fillMaxSize(),
) { innerPadding ->
diff --git a/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt b/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt
new file mode 100644
index 0000000..d789ce7
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt
@@ -0,0 +1,10 @@
+package org.sopt.and.presentation.search
+
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class SearchViewModel @Inject constructor(
+) : ViewModel() {
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt
similarity index 96%
rename from app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt
rename to app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt
index a1da24a..eaf270c 100644
--- a/app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt
+++ b/app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt
@@ -1,4 +1,4 @@
-package org.sopt.and.feature.signup
+package org.sopt.and.presentation.signup
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.background
@@ -46,13 +46,13 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import org.sopt.and.R
-import org.sopt.and.core.designsystem.component.AuthTextField
-import org.sopt.and.core.designsystem.component.ErrorDialog
-import org.sopt.and.core.designsystem.component.WavveSignUpButton
-import org.sopt.and.data.remote.model.request.UserSignUpRequestDto
+import org.sopt.and.domain.model.request.UserSignUpModel
+import org.sopt.and.presentation.core.component.AuthTextField
+import org.sopt.and.presentation.core.component.ErrorDialog
+import org.sopt.and.presentation.core.component.WavveSignUpButton
import org.sopt.and.ui.theme.WavveTheme
import org.sopt.and.utils.noRippleClickable
@@ -61,7 +61,7 @@ import org.sopt.and.utils.noRippleClickable
fun SignUpScreen(
navController: NavController,
onSignUpSuccess: (String, String) -> Unit,
- viewModel: SignUpViewModel = viewModel()
+ viewModel: SignUpViewModel = hiltViewModel()
) {
val focusManager = LocalFocusManager.current
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
@@ -243,7 +243,7 @@ fun SignUpScreen(
onSuccess = { userName, password, hobby ->
viewModel.postUserSignUp(
context = context,
- body = UserSignUpRequestDto(
+ body = UserSignUpModel(
username = userName,
password = password,
hobby = hobby
diff --git a/app/src/main/java/org/sopt/and/presentation/signup/SignUpState.kt b/app/src/main/java/org/sopt/and/presentation/signup/SignUpState.kt
new file mode 100644
index 0000000..d4d0863
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/presentation/signup/SignUpState.kt
@@ -0,0 +1,10 @@
+package org.sopt.and.presentation.signup
+
+import org.sopt.and.domain.model.response.UserNumber
+
+sealed class SignUpState {
+ data object Idle : SignUpState()
+ data object Loading : SignUpState()
+ data class Success(val result: UserNumber) : SignUpState()
+ data class Failure(val message: String) : SignUpState()
+}
diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt
similarity index 66%
rename from app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt
rename to app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt
index c46883f..38e3020 100644
--- a/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt
+++ b/app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt
@@ -1,7 +1,6 @@
-package org.sopt.and.feature.signup
+package org.sopt.and.presentation.signup
import android.content.Context
-import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -10,47 +9,42 @@ 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.launch
import org.sopt.and.R
-import org.sopt.and.data.ServicePool
-import org.sopt.and.data.remote.model.request.UserSignUpRequestDto
-import org.sopt.and.data.remote.model.response.UserSignUpResponseDto
+import org.sopt.and.domain.model.request.UserSignUpModel
+import org.sopt.and.domain.repository.UserRepository
import org.sopt.and.utils.AuthKey.PASSWORD_PATTERN
-import org.sopt.and.utils.toast
-import retrofit2.HttpException
-import java.io.IOException
-
-class SignUpViewModel : ViewModel() {
- private val userService by lazy { ServicePool.userService }
-
- private val _userState = mutableStateOf(null)
- val userState: State get() = _userState
-
- fun postUserSignUp(context: Context, body: UserSignUpRequestDto) {
+import org.sopt.and.utils.handleErrorToast
+import javax.inject.Inject
+
+@HiltViewModel
+class SignUpViewModel @Inject constructor(
+ private val userRepository: UserRepository
+) : ViewModel() {
+ private val _signUpState = mutableStateOf(SignUpState.Idle)
+ val signUpState: State get() = _signUpState
+
+ fun postUserSignUp(context: Context, body: UserSignUpModel) {
+ _signUpState.value = SignUpState.Loading
viewModelScope.launch {
- runCatching {
- userService.postUserSignUp(body = body)
- }.onSuccess { response ->
- _userState.value = response.result
- }.onFailure { error ->
- when (error) {
- is HttpException -> {
- when (error.code()) {
- 400 -> context.toast(context.getString(R.string.fail_to_signup_maximum_length))
- 409 -> context.toast(context.getString(R.string.fail_to_signup_duplicate_name))
- }
- }
-
- is IOException -> {
- context.toast(context.getString(R.string.fail_to_network))
- }
-
- else -> {
- context.toast(context.getString(R.string.fail_to_signup))
- }
+ val result = userRepository.postUserSignUp(
+ userSignUpModel = body
+ )
+ _signUpState.value = result.fold(
+ onSuccess = { response ->
+ SignUpState.Success(response)
+ },
+ onFailure = { error ->
+ handleErrorToast(
+ exception = error,
+ is400Error = R.string.fail_to_signup_maximum_length,
+ is409Error = R.string.fail_to_signup_duplicate_name,
+ context = context
+ )
+ SignUpState.Failure(error.message.orEmpty())
}
- Log.e("postUserSignUpError", error.toString())
- }
+ )
}
}
diff --git a/app/src/main/java/org/sopt/and/utils/ErrorHandler.kt b/app/src/main/java/org/sopt/and/utils/ErrorHandler.kt
new file mode 100644
index 0000000..551501a
--- /dev/null
+++ b/app/src/main/java/org/sopt/and/utils/ErrorHandler.kt
@@ -0,0 +1,31 @@
+package org.sopt.and.utils
+
+import android.content.Context
+import org.sopt.and.R
+import retrofit2.HttpException
+import java.io.IOException
+
+fun handleErrorToast(
+ exception: Throwable?,
+ is400Error: Int,
+ context: Context,
+ is403Error: Int = R.string.fail_to_network,
+ is409Error: Int = R.string.fail_to_network,
+) {
+ return when (exception) {
+ is HttpException -> when (exception.code()) {
+ 400 -> context.toast(context.getString(is400Error))
+ 403 -> context.toast(context.getString(is403Error))
+ 409 -> context.toast(context.getString(is409Error))
+ else -> context.toast(context.getString(R.string.fail_to_network))
+ }
+
+ is IOException -> {
+ context.toast(context.getString(R.string.fail_to_network))
+ }
+
+ else -> {
+ context.toast(context.getString(R.string.fail_to_login))
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2737244..a5697db 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -18,6 +18,9 @@ okhttp = "4.11.0"
retrofit = "2.9.0"
retrofitKotlinSerializationConverter = "1.0.0"
kotlinxSerializationJson = "1.6.3"
+# hilt
+androidx-hilt-navigation-compose = "1.2.0"
+hilt = "2.51.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -46,9 +49,14 @@ okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-i
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" }
+# hilt
+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" }
+hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidx-hilt-navigation-compose" }
[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