diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2a39dff..6d480c5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -85,4 +85,7 @@ dependencies { // Network implementation(platform(libs.okhttp.bom)) implementation(libs.bundles.network) + + //timer + implementation(libs.timber) } diff --git a/app/src/main/java/org/sopt/and/component/AuthTextField.kt b/app/src/main/java/org/sopt/and/core/component/AuthTextField.kt similarity index 98% rename from app/src/main/java/org/sopt/and/component/AuthTextField.kt rename to app/src/main/java/org/sopt/and/core/component/AuthTextField.kt index 790e103..ab52fc0 100644 --- a/app/src/main/java/org/sopt/and/component/AuthTextField.kt +++ b/app/src/main/java/org/sopt/and/core/component/AuthTextField.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.core.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/org/sopt/and/component/ExpandedButton.kt b/app/src/main/java/org/sopt/and/core/component/ExpandedButton.kt similarity index 97% rename from app/src/main/java/org/sopt/and/component/ExpandedButton.kt rename to app/src/main/java/org/sopt/and/core/component/ExpandedButton.kt index 56aca03..88f8f8f 100644 --- a/app/src/main/java/org/sopt/and/component/ExpandedButton.kt +++ b/app/src/main/java/org/sopt/and/core/component/ExpandedButton.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.core.component import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/app/src/main/java/org/sopt/and/component/RoundedButton.kt b/app/src/main/java/org/sopt/and/core/component/RoundedButton.kt similarity index 97% rename from app/src/main/java/org/sopt/and/component/RoundedButton.kt rename to app/src/main/java/org/sopt/and/core/component/RoundedButton.kt index 93246d4..516a840 100644 --- a/app/src/main/java/org/sopt/and/component/RoundedButton.kt +++ b/app/src/main/java/org/sopt/and/core/component/RoundedButton.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.core.component import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/app/src/main/java/org/sopt/and/component/TopBar.kt b/app/src/main/java/org/sopt/and/core/component/TopBar.kt similarity index 97% rename from app/src/main/java/org/sopt/and/component/TopBar.kt rename to app/src/main/java/org/sopt/and/core/component/TopBar.kt index b2a38cc..003d626 100644 --- a/app/src/main/java/org/sopt/and/component/TopBar.kt +++ b/app/src/main/java/org/sopt/and/core/component/TopBar.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.core.component import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize diff --git a/app/src/main/java/org/sopt/and/Extension.kt b/app/src/main/java/org/sopt/and/core/extension/ContextExtension.kt similarity index 85% rename from app/src/main/java/org/sopt/and/Extension.kt rename to app/src/main/java/org/sopt/and/core/extension/ContextExtension.kt index 3b765c4..9fe2a89 100644 --- a/app/src/main/java/org/sopt/and/Extension.kt +++ b/app/src/main/java/org/sopt/and/core/extension/ContextExtension.kt @@ -1,4 +1,4 @@ -package org.sopt.and +package org.sopt.and.core.extension import android.content.Context import android.widget.Toast diff --git a/app/src/main/java/org/sopt/and/core/navigation/MainTabRoute.kt b/app/src/main/java/org/sopt/and/core/navigation/MainTabRoute.kt new file mode 100644 index 0000000..98b4ab3 --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/navigation/MainTabRoute.kt @@ -0,0 +1,3 @@ +package org.sopt.and.core.navigation + +interface MainTabRoute : Route diff --git a/app/src/main/java/org/sopt/and/core/navigation/Route.kt b/app/src/main/java/org/sopt/and/core/navigation/Route.kt new file mode 100644 index 0000000..292dd41 --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/navigation/Route.kt @@ -0,0 +1,3 @@ +package org.sopt.and.core.navigation + +interface Route diff --git a/app/src/main/java/org/sopt/and/core/util/BaseViewModel.kt b/app/src/main/java/org/sopt/and/core/util/BaseViewModel.kt new file mode 100644 index 0000000..513ec83 --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/util/BaseViewModel.kt @@ -0,0 +1,52 @@ +package org.sopt.and.core.util + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +abstract class BaseViewModel() : + ViewModel() { + private val initialState: State by lazy { createInitialState() } + abstract fun createInitialState(): State + + private val _state = MutableStateFlow(initialState) + val uiState: StateFlow + get() = _state.asStateFlow() + val currentState: State + get() = uiState.value + + private val _event: MutableSharedFlow = MutableSharedFlow() + val event: SharedFlow + get() = _event.asSharedFlow() + + private val _sideEffect: Channel = Channel() + val sideEffect: Flow + get() = _sideEffect.receiveAsFlow() + + fun setState(reduce: State.() -> State) { + _state.value = currentState.reduce() + } + + open fun setIntent(intent: Intent) { + dispatchIntent(intent) + } + + private fun dispatchIntent(intent: Intent) = viewModelScope.launch { + handleEvent(intent) + } + + protected abstract suspend fun handleEvent(intent: Intent) + + fun setSideEffect(sideEffect: () -> SideEffect) { + viewModelScope.launch { _sideEffect.send(sideEffect()) } + } +} diff --git a/app/src/main/java/org/sopt/and/core/util/LoadState.kt b/app/src/main/java/org/sopt/and/core/util/LoadState.kt new file mode 100644 index 0000000..8cfcf6e --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/util/LoadState.kt @@ -0,0 +1,8 @@ +package org.sopt.and.core.util + +enum class LoadState { + Idle, + Loading, + Success, + Failure +} diff --git a/app/src/main/java/org/sopt/and/core/util/UiEvent.kt b/app/src/main/java/org/sopt/and/core/util/UiEvent.kt new file mode 100644 index 0000000..0540a39 --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/util/UiEvent.kt @@ -0,0 +1,3 @@ +package org.sopt.and.core.util + +interface UiEvent diff --git a/app/src/main/java/org/sopt/and/core/util/UiSideEffect.kt b/app/src/main/java/org/sopt/and/core/util/UiSideEffect.kt new file mode 100644 index 0000000..ded471b --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/util/UiSideEffect.kt @@ -0,0 +1,3 @@ +package org.sopt.and.core.util + +interface UiSideEffect diff --git a/app/src/main/java/org/sopt/and/core/util/UiState.kt b/app/src/main/java/org/sopt/and/core/util/UiState.kt new file mode 100644 index 0000000..56c7b3c --- /dev/null +++ b/app/src/main/java/org/sopt/and/core/util/UiState.kt @@ -0,0 +1,3 @@ +package org.sopt.and.core.util + +interface UiState diff --git a/app/src/main/java/org/sopt/and/data/datasource/WavveDataSource.kt b/app/src/main/java/org/sopt/and/data/datasource/AuthDataSource.kt similarity index 76% rename from app/src/main/java/org/sopt/and/data/datasource/WavveDataSource.kt rename to app/src/main/java/org/sopt/and/data/datasource/AuthDataSource.kt index 6477aad..4abdf08 100644 --- a/app/src/main/java/org/sopt/and/data/datasource/WavveDataSource.kt +++ b/app/src/main/java/org/sopt/and/data/datasource/AuthDataSource.kt @@ -3,12 +3,10 @@ package org.sopt.and.data.datasource import org.sopt.and.data.dto.request.RequestSignInDto import org.sopt.and.data.dto.request.RequestSignUpDto import org.sopt.and.data.dto.response.BaseResponse -import org.sopt.and.data.dto.response.ResponseUserHobbyDto import org.sopt.and.data.dto.response.ResponseSignInDto import org.sopt.and.data.dto.response.ResponseSignUpDto -interface WavveDataSource { +interface AuthDataSource { suspend fun postSignUp(requestSignUpDto: RequestSignUpDto): BaseResponse suspend fun postSignIn(requestSignInDto: RequestSignInDto): BaseResponse - suspend fun getUserHobby(): BaseResponse } diff --git a/app/src/main/java/org/sopt/and/data/datasource/MyDataSource.kt b/app/src/main/java/org/sopt/and/data/datasource/MyDataSource.kt new file mode 100644 index 0000000..821fba6 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datasource/MyDataSource.kt @@ -0,0 +1,8 @@ +package org.sopt.and.data.datasource + +import org.sopt.and.data.dto.response.BaseResponse +import org.sopt.and.data.dto.response.ResponseUserHobbyDto + +interface MyDataSource { + suspend fun getUserHobby(): BaseResponse +} diff --git a/app/src/main/java/org/sopt/and/data/datasourceimpl/WavveDataSourceImpl.kt b/app/src/main/java/org/sopt/and/data/datasourceimpl/AuthDataSourceImpl.kt similarity index 53% rename from app/src/main/java/org/sopt/and/data/datasourceimpl/WavveDataSourceImpl.kt rename to app/src/main/java/org/sopt/and/data/datasourceimpl/AuthDataSourceImpl.kt index db581fa..3ec8307 100644 --- a/app/src/main/java/org/sopt/and/data/datasourceimpl/WavveDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/and/data/datasourceimpl/AuthDataSourceImpl.kt @@ -1,25 +1,20 @@ package org.sopt.and.data.datasourceimpl -import org.sopt.and.data.datasource.WavveDataSource +import org.sopt.and.data.datasource.AuthDataSource import org.sopt.and.data.dto.request.RequestSignInDto import org.sopt.and.data.dto.request.RequestSignUpDto import org.sopt.and.data.dto.response.BaseResponse -import org.sopt.and.data.dto.response.ResponseUserHobbyDto import org.sopt.and.data.dto.response.ResponseSignInDto import org.sopt.and.data.dto.response.ResponseSignUpDto -import org.sopt.and.data.service.WavveService +import org.sopt.and.data.service.AuthService import javax.inject.Inject -class WavveDataSourceImpl @Inject constructor( - private val wavveService: WavveService -) : WavveDataSource { +class AuthDataSourceImpl @Inject constructor( + private val authService: AuthService +) : AuthDataSource { override suspend fun postSignUp(requestSignUpDto: RequestSignUpDto): BaseResponse = - wavveService.postSignUp(requestSignUpDto) + authService.postSignUp(requestSignUpDto) override suspend fun postSignIn(requestSignInDto: RequestSignInDto): BaseResponse = - wavveService.postSignIn(requestSignInDto) - - override suspend fun getUserHobby(): BaseResponse = - wavveService.getUserHobby() - + authService.postSignIn(requestSignInDto) } diff --git a/app/src/main/java/org/sopt/and/data/datasourceimpl/MyDataSourceImpl.kt b/app/src/main/java/org/sopt/and/data/datasourceimpl/MyDataSourceImpl.kt new file mode 100644 index 0000000..fcaf119 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datasourceimpl/MyDataSourceImpl.kt @@ -0,0 +1,14 @@ +package org.sopt.and.data.datasourceimpl + +import org.sopt.and.data.datasource.MyDataSource +import org.sopt.and.data.dto.response.BaseResponse +import org.sopt.and.data.dto.response.ResponseUserHobbyDto +import org.sopt.and.data.service.MyService +import javax.inject.Inject + +class MyDataSourceImpl @Inject constructor( + private val myService: MyService +) : MyDataSource { + override suspend fun getUserHobby(): BaseResponse = + myService.getUserHobby() +} diff --git a/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt b/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt index 62ef271..84ae88c 100644 --- a/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt +++ b/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt @@ -4,8 +4,10 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import org.sopt.and.data.datasource.WavveDataSource -import org.sopt.and.data.datasourceimpl.WavveDataSourceImpl +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.datasource.MyDataSource +import org.sopt.and.data.datasourceimpl.AuthDataSourceImpl +import org.sopt.and.data.datasourceimpl.MyDataSourceImpl import javax.inject.Singleton @Module @@ -13,6 +15,10 @@ import javax.inject.Singleton internal abstract class DataSourceModule { @Binds @Singleton - abstract fun bindsDataSource(myDataSourceImpl: WavveDataSourceImpl): WavveDataSource + abstract fun bindsAuthDataSource(authDataSourceImpl: AuthDataSourceImpl): AuthDataSource + + @Binds + @Singleton + abstract fun bindsMyDataSource(MyDataSourceImpl: MyDataSourceImpl): MyDataSource } diff --git a/app/src/main/java/org/sopt/and/data/di/NetworkModule.kt b/app/src/main/java/org/sopt/and/data/di/NetworkModule.kt index 2085458..aab8c61 100644 --- a/app/src/main/java/org/sopt/and/data/di/NetworkModule.kt +++ b/app/src/main/java/org/sopt/and/data/di/NetworkModule.kt @@ -13,6 +13,7 @@ import okhttp3.logging.HttpLoggingInterceptor import org.sopt.and.BuildConfig import org.sopt.and.sharedpreference.User import retrofit2.Retrofit +import timber.log.Timber import javax.inject.Singleton @Module @@ -36,13 +37,12 @@ object NetworkModule { @Provides fun provideLoggingInterceptor(): HttpLoggingInterceptor { return HttpLoggingInterceptor { message -> - Log.d("Retrofit2", "CONNECTION INFO -> $message") + Timber.tag("Retrofit2").d("CONNECTION INFO -> $message") }.apply { level = HttpLoggingInterceptor.Level.BODY } } - @Singleton @Provides fun provideAuthInterceptor(user: User): AuthInterceptor { diff --git a/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt b/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt index fe12ba9..16d6588 100644 --- a/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt +++ b/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt @@ -4,8 +4,12 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import org.sopt.and.data.repositoryimpl.WavveRepositoryImpl -import org.sopt.and.domain.repository.WavveRepository +import org.sopt.and.data.repositoryimpl.MyRepositoryImpl +import org.sopt.and.data.repositoryimpl.SignInRepositoryImpl +import org.sopt.and.data.repositoryimpl.SignUpRepositoryImpl +import org.sopt.and.domain.repository.MyRepository +import org.sopt.and.domain.repository.SignInRepository +import org.sopt.and.domain.repository.SignUpRepository import javax.inject.Singleton @Module @@ -13,7 +17,19 @@ import javax.inject.Singleton internal abstract class RepositoryModule { @Binds @Singleton - abstract fun bindsRepository( - myRepositoryImpl: WavveRepositoryImpl - ): WavveRepository + abstract fun bindsSignInRepository( + signInRepositoryImpl: SignInRepositoryImpl + ): SignInRepository + + @Binds + @Singleton + abstract fun bindsSignUpRepository( + signUpRepositoryImpl: SignUpRepositoryImpl + ): SignUpRepository + + @Binds + @Singleton + abstract fun bindsMyRepository( + myRepositoryImpl: MyRepositoryImpl + ): MyRepository } diff --git a/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt b/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt index 3933554..2a72638 100644 --- a/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt +++ b/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt @@ -4,7 +4,8 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import org.sopt.and.data.service.WavveService +import org.sopt.and.data.service.AuthService +import org.sopt.and.data.service.MyService import retrofit2.Retrofit import javax.inject.Singleton @@ -13,6 +14,11 @@ import javax.inject.Singleton object ServiceModule { @Provides @Singleton - fun providerService(retrofit: Retrofit): WavveService = - retrofit.create(WavveService::class.java) + fun providerAuthService(retrofit: Retrofit): AuthService = + retrofit.create(AuthService::class.java) + + @Provides + @Singleton + fun providerMyService(retrofit: Retrofit): MyService = + retrofit.create(MyService::class.java) } diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/MyRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/MyRepositoryImpl.kt new file mode 100644 index 0000000..681c3ae --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/MyRepositoryImpl.kt @@ -0,0 +1,14 @@ +package org.sopt.and.data.repositoryimpl + +import org.sopt.and.data.datasource.MyDataSource +import org.sopt.and.domain.entity.response.ResponseHobbyEntity +import org.sopt.and.domain.repository.MyRepository +import javax.inject.Inject + +class MyRepositoryImpl @Inject constructor( + private val myDataSource: MyDataSource +) : MyRepository { + override suspend fun getHobby(): Result = runCatching { + myDataSource.getUserHobby().result.toEntity() + } +} diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/SignInRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignInRepositoryImpl.kt new file mode 100644 index 0000000..4a5ed3f --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignInRepositoryImpl.kt @@ -0,0 +1,17 @@ +package org.sopt.and.data.repositoryimpl + +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.dto.request.toDto +import org.sopt.and.domain.entity.request.RequestSignInEntity +import org.sopt.and.domain.entity.response.ResponseSignInEntity +import org.sopt.and.domain.repository.SignInRepository +import javax.inject.Inject + +class SignInRepositoryImpl @Inject constructor( + private val authDataSource: AuthDataSource +) : SignInRepository { + override suspend fun signIn(body: RequestSignInEntity): Result = + runCatching { + authDataSource.postSignIn(body.toDto()).result.toEntity() + } +} diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/SignUpRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignUpRepositoryImpl.kt new file mode 100644 index 0000000..ffb3bd5 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignUpRepositoryImpl.kt @@ -0,0 +1,17 @@ +package org.sopt.and.data.repositoryimpl + +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.dto.request.toDto +import org.sopt.and.domain.entity.request.RequestSignUpEntity +import org.sopt.and.domain.entity.response.ResponseSignUpEntity +import org.sopt.and.domain.repository.SignUpRepository +import javax.inject.Inject + +class SignUpRepositoryImpl @Inject constructor( + private val authDataSource: AuthDataSource +) : SignUpRepository { + override suspend fun signUp(body: RequestSignUpEntity): Result = + runCatching { + authDataSource.postSignUp(body.toDto()).result.toEntity() + } +} diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/WavveRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/WavveRepositoryImpl.kt deleted file mode 100644 index 90169c6..0000000 --- a/app/src/main/java/org/sopt/and/data/repositoryimpl/WavveRepositoryImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.sopt.and.data.repositoryimpl - -import org.sopt.and.data.datasource.WavveDataSource -import org.sopt.and.data.dto.request.toDto -import org.sopt.and.domain.entity.request.RequestSignInEntity -import org.sopt.and.domain.entity.request.RequestSignUpEntity -import org.sopt.and.domain.entity.response.ResponseHobbyEntity -import org.sopt.and.domain.entity.response.ResponseSignInEntity -import org.sopt.and.domain.entity.response.ResponseSignUpEntity -import org.sopt.and.domain.repository.WavveRepository -import javax.inject.Inject - -class WavveRepositoryImpl @Inject constructor( - private val wavveDataSource: WavveDataSource -) : WavveRepository { - override suspend fun signUp(body: RequestSignUpEntity): Result = runCatching { - wavveDataSource.postSignUp(body.toDto()).result.toEntity() - } - - override suspend fun signIn(body: RequestSignInEntity): Result = runCatching { - wavveDataSource.postSignIn(body.toDto()).result.toEntity() - } - - override suspend fun getHobby(): Result = runCatching { - wavveDataSource.getUserHobby().result.toEntity() - } -} diff --git a/app/src/main/java/org/sopt/and/data/service/WavveService.kt b/app/src/main/java/org/sopt/and/data/service/AuthService.kt similarity index 74% rename from app/src/main/java/org/sopt/and/data/service/WavveService.kt rename to app/src/main/java/org/sopt/and/data/service/AuthService.kt index 79a306f..afd81d0 100644 --- a/app/src/main/java/org/sopt/and/data/service/WavveService.kt +++ b/app/src/main/java/org/sopt/and/data/service/AuthService.kt @@ -3,14 +3,12 @@ package org.sopt.and.data.service import org.sopt.and.data.dto.request.RequestSignInDto import org.sopt.and.data.dto.request.RequestSignUpDto import org.sopt.and.data.dto.response.BaseResponse -import org.sopt.and.data.dto.response.ResponseUserHobbyDto import org.sopt.and.data.dto.response.ResponseSignInDto import org.sopt.and.data.dto.response.ResponseSignUpDto import retrofit2.http.Body -import retrofit2.http.GET import retrofit2.http.POST -interface WavveService { +interface AuthService { @POST("/user") suspend fun postSignUp( @Body body: RequestSignUpDto @@ -20,7 +18,4 @@ interface WavveService { suspend fun postSignIn( @Body body: RequestSignInDto ): BaseResponse - - @GET("/user/my-hobby") - suspend fun getUserHobby(): BaseResponse } diff --git a/app/src/main/java/org/sopt/and/data/service/MyService.kt b/app/src/main/java/org/sopt/and/data/service/MyService.kt new file mode 100644 index 0000000..806a278 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/service/MyService.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.service + +import org.sopt.and.data.dto.response.BaseResponse +import org.sopt.and.data.dto.response.ResponseUserHobbyDto +import retrofit2.http.GET + +interface MyService { + @GET("/user/my-hobby") + suspend fun getUserHobby(): BaseResponse +} diff --git a/app/src/main/java/org/sopt/and/domain/entity/response/ResponseHobbyEntity.kt b/app/src/main/java/org/sopt/and/domain/entity/response/ResponseHobbyEntity.kt index 61feb7b..6f74d1b 100644 --- a/app/src/main/java/org/sopt/and/domain/entity/response/ResponseHobbyEntity.kt +++ b/app/src/main/java/org/sopt/and/domain/entity/response/ResponseHobbyEntity.kt @@ -1,5 +1,5 @@ package org.sopt.and.domain.entity.response data class ResponseHobbyEntity( - val hobby: String + val hobby: String = "" ) diff --git a/app/src/main/java/org/sopt/and/domain/repository/MyRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/MyRepository.kt new file mode 100644 index 0000000..708122c --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/MyRepository.kt @@ -0,0 +1,7 @@ +package org.sopt.and.domain.repository + +import org.sopt.and.domain.entity.response.ResponseHobbyEntity + +interface MyRepository { + suspend fun getHobby(): Result +} diff --git a/app/src/main/java/org/sopt/and/domain/repository/SignInRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/SignInRepository.kt new file mode 100644 index 0000000..853a70f --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/SignInRepository.kt @@ -0,0 +1,8 @@ +package org.sopt.and.domain.repository + +import org.sopt.and.domain.entity.request.RequestSignInEntity +import org.sopt.and.domain.entity.response.ResponseSignInEntity + +interface SignInRepository { + suspend fun signIn(body: RequestSignInEntity): Result +} diff --git a/app/src/main/java/org/sopt/and/domain/repository/SignUpRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/SignUpRepository.kt new file mode 100644 index 0000000..d6dccaf --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/SignUpRepository.kt @@ -0,0 +1,8 @@ +package org.sopt.and.domain.repository + +import org.sopt.and.domain.entity.request.RequestSignUpEntity +import org.sopt.and.domain.entity.response.ResponseSignUpEntity + +interface SignUpRepository { + suspend fun signUp(body: RequestSignUpEntity): Result +} diff --git a/app/src/main/java/org/sopt/and/domain/repository/WavveRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/WavveRepository.kt deleted file mode 100644 index 6900eab..0000000 --- a/app/src/main/java/org/sopt/and/domain/repository/WavveRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.and.domain.repository - -import org.sopt.and.domain.entity.request.RequestSignInEntity -import org.sopt.and.domain.entity.request.RequestSignUpEntity -import org.sopt.and.domain.entity.response.ResponseHobbyEntity -import org.sopt.and.domain.entity.response.ResponseSignInEntity -import org.sopt.and.domain.entity.response.ResponseSignUpEntity - -interface WavveRepository { - suspend fun signUp(body: RequestSignUpEntity): Result - suspend fun signIn(body:RequestSignInEntity): Result - suspend fun getHobby(): Result -} diff --git a/app/src/main/java/org/sopt/and/domain/usecase/MyUseCase.kt b/app/src/main/java/org/sopt/and/domain/usecase/MyUseCase.kt new file mode 100644 index 0000000..224b23f --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/usecase/MyUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.and.domain.usecase + +import org.sopt.and.domain.entity.response.ResponseHobbyEntity +import org.sopt.and.domain.repository.MyRepository +import javax.inject.Inject + +class MyUseCase @Inject constructor( + private val myRepository: MyRepository +) { + suspend operator fun invoke(): Result = + myRepository.getHobby() +} diff --git a/app/src/main/java/org/sopt/and/domain/usecase/SignInUseCase.kt b/app/src/main/java/org/sopt/and/domain/usecase/SignInUseCase.kt new file mode 100644 index 0000000..3fb75ff --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/usecase/SignInUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.and.domain.usecase + +import org.sopt.and.domain.entity.request.RequestSignInEntity +import org.sopt.and.domain.entity.response.ResponseSignInEntity +import org.sopt.and.domain.repository.SignInRepository +import javax.inject.Inject + +class SignInUseCase @Inject constructor( + private val signInRepository: SignInRepository +) { + suspend operator fun invoke(signInEntity: RequestSignInEntity): Result = + signInRepository.signIn(signInEntity) +} diff --git a/app/src/main/java/org/sopt/and/domain/usecase/SignUpUseCase.kt b/app/src/main/java/org/sopt/and/domain/usecase/SignUpUseCase.kt new file mode 100644 index 0000000..ba10919 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/usecase/SignUpUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.and.domain.usecase + +import org.sopt.and.domain.entity.request.RequestSignUpEntity +import org.sopt.and.domain.entity.response.ResponseSignUpEntity +import org.sopt.and.domain.repository.SignUpRepository +import javax.inject.Inject + +class SignUpUseCase @Inject constructor( + private val signUpRepository: SignUpRepository +) { + suspend operator fun invoke(signUpEntity: RequestSignUpEntity): Result = + signUpRepository.signUp(signUpEntity) +} diff --git a/app/src/main/java/org/sopt/and/feature/home/HomeNavigation.kt b/app/src/main/java/org/sopt/and/feature/home/HomeNavigation.kt index d7cab1a..f3aa396 100644 --- a/app/src/main/java/org/sopt/and/feature/home/HomeNavigation.kt +++ b/app/src/main/java/org/sopt/and/feature/home/HomeNavigation.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.serialization.Serializable -import org.sopt.and.feature.main.MainTabRoute +import org.sopt.and.core.navigation.MainTabRoute fun NavController.navigateHome( navOptions: NavOptions diff --git a/app/src/main/java/org/sopt/and/feature/home/HomeRoute.kt b/app/src/main/java/org/sopt/and/feature/home/HomeRoute.kt index 127af7d..6ea690a 100644 --- a/app/src/main/java/org/sopt/and/feature/home/HomeRoute.kt +++ b/app/src/main/java/org/sopt/and/feature/home/HomeRoute.kt @@ -33,8 +33,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import org.sopt.and.R -import org.sopt.and.component.HomeContent -import org.sopt.and.component.TopBar +import org.sopt.and.core.component.TopBar +import org.sopt.and.feature.home.component.HomeContent import org.sopt.and.feature.home.model.ContentModel import org.sopt.and.ui.theme.ANDANDROIDTheme import org.sopt.and.ui.theme.Black diff --git a/app/src/main/java/org/sopt/and/component/HomeContent.kt b/app/src/main/java/org/sopt/and/feature/home/component/HomeContent.kt similarity index 99% rename from app/src/main/java/org/sopt/and/component/HomeContent.kt rename to app/src/main/java/org/sopt/and/feature/home/component/HomeContent.kt index d67c2cf..277fcff 100644 --- a/app/src/main/java/org/sopt/and/component/HomeContent.kt +++ b/app/src/main/java/org/sopt/and/feature/home/component/HomeContent.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.feature.home.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/org/sopt/and/feature/main/MainBottomTab.kt b/app/src/main/java/org/sopt/and/feature/main/MainBottomTab.kt index bf219b2..97c99f3 100644 --- a/app/src/main/java/org/sopt/and/feature/main/MainBottomTab.kt +++ b/app/src/main/java/org/sopt/and/feature/main/MainBottomTab.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Search import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector +import org.sopt.and.core.navigation.MainTabRoute enum class MainBottomTab( val icon: ImageVector, @@ -35,7 +36,7 @@ enum class MainBottomTab( } @Composable - fun contains(predicate: @Composable (MainRoute) -> Boolean): Boolean { + fun contains(predicate: @Composable (MainTabRoute) -> Boolean): Boolean { return entries.map { it.route }.any { predicate(it) } } } diff --git a/app/src/main/java/org/sopt/and/feature/main/MainNavigation.kt b/app/src/main/java/org/sopt/and/feature/main/MainNavigation.kt index ccdaf6e..c53206e 100644 --- a/app/src/main/java/org/sopt/and/feature/main/MainNavigation.kt +++ b/app/src/main/java/org/sopt/and/feature/main/MainNavigation.kt @@ -8,6 +8,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions +import org.sopt.and.core.navigation.MainTabRoute import org.sopt.and.feature.home.Home import org.sopt.and.feature.home.navigateHome import org.sopt.and.feature.my.navigateMy diff --git a/app/src/main/java/org/sopt/and/feature/main/MainRoute.kt b/app/src/main/java/org/sopt/and/feature/main/MainRoute.kt deleted file mode 100644 index 65c9692..0000000 --- a/app/src/main/java/org/sopt/and/feature/main/MainRoute.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.sopt.and.feature.main - -interface MainRoute - -interface MainTabRoute : MainRoute diff --git a/app/src/main/java/org/sopt/and/feature/my/MyContract.kt b/app/src/main/java/org/sopt/and/feature/my/MyContract.kt new file mode 100644 index 0000000..6cccbac --- /dev/null +++ b/app/src/main/java/org/sopt/and/feature/my/MyContract.kt @@ -0,0 +1,25 @@ +package org.sopt.and.feature.my + +import org.sopt.and.core.util.LoadState +import org.sopt.and.core.util.UiEvent +import org.sopt.and.core.util.UiSideEffect +import org.sopt.and.core.util.UiState +import org.sopt.and.domain.entity.response.ResponseHobbyEntity + +class MyContract { + data class MyState( + val uiState: LoadState = LoadState.Loading, + val profile: ResponseHobbyEntity = ResponseHobbyEntity() + ) : UiState + + sealed class MySideEffect : UiSideEffect { + data class ShowToast(val toastMessage: Int) : MySideEffect() + } + + sealed class MyEvent : UiEvent { + data class FetchMyHobby( + val uiState: LoadState, + val userInformation: ResponseHobbyEntity + ) : MyEvent() + } +} diff --git a/app/src/main/java/org/sopt/and/feature/my/MyNavigation.kt b/app/src/main/java/org/sopt/and/feature/my/MyNavigation.kt index 15739a3..797ecd7 100644 --- a/app/src/main/java/org/sopt/and/feature/my/MyNavigation.kt +++ b/app/src/main/java/org/sopt/and/feature/my/MyNavigation.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.serialization.Serializable -import org.sopt.and.feature.main.MainTabRoute +import org.sopt.and.core.navigation.MainTabRoute fun NavController.navigateMy( navOptions: NavOptions diff --git a/app/src/main/java/org/sopt/and/feature/my/MyRoute.kt b/app/src/main/java/org/sopt/and/feature/my/MyRoute.kt index ab0a44c..51c4ef6 100644 --- a/app/src/main/java/org/sopt/and/feature/my/MyRoute.kt +++ b/app/src/main/java/org/sopt/and/feature/my/MyRoute.kt @@ -21,9 +21,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.traceEventEnd import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow @@ -31,10 +31,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.flowWithLifecycle import org.sopt.and.R -import org.sopt.and.component.EventContent -import org.sopt.and.component.HistoryContent +import org.sopt.and.core.extension.showToast +import org.sopt.and.core.util.LoadState +import org.sopt.and.feature.my.component.EventContent +import org.sopt.and.feature.my.component.HistoryContent import org.sopt.and.ui.theme.ANDANDROIDTheme import org.sopt.and.ui.theme.Black import org.sopt.and.ui.theme.Blue @@ -48,17 +52,42 @@ fun MyRoute( modifier: Modifier = Modifier, viewModel: MyViewModel = hiltViewModel() ) { - val state by viewModel.state.collectAsStateWithLifecycle() + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val state by viewModel.uiState.collectAsStateWithLifecycle() LaunchedEffect(true) { - viewModel.getUserHobby() + // usecase 사용했을 때, + viewModel.fetchUserHobby() + + // usecase 사용안했을 때, + viewModel.getUserHobbyRepository() } - MyScreen( - paddingValues = paddingValues, - hobby = state.hobby, - modifier = modifier - ) + LaunchedEffect(viewModel.sideEffect, lifecycleOwner) { + viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle) + .collect { mySideEffect -> + when (mySideEffect) { + is MyContract.MySideEffect.ShowToast -> context.showToast(message = mySideEffect.toastMessage) + } + } + } + + when (state.uiState) { + LoadState.Idle -> {} + + LoadState.Loading -> {} + + LoadState.Success -> { + MyScreen( + paddingValues = paddingValues, + hobby = state.profile.hobby, + modifier = modifier + ) + } + + LoadState.Failure -> {} + } } @Composable diff --git a/app/src/main/java/org/sopt/and/feature/my/MySideEffect.kt b/app/src/main/java/org/sopt/and/feature/my/MySideEffect.kt deleted file mode 100644 index 35a6656..0000000 --- a/app/src/main/java/org/sopt/and/feature/my/MySideEffect.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.sopt.and.feature.my - -sealed class MySideEffect { - data class ShowToast(val toastMessage: Int) : MySideEffect() -} diff --git a/app/src/main/java/org/sopt/and/feature/my/MyState.kt b/app/src/main/java/org/sopt/and/feature/my/MyState.kt deleted file mode 100644 index 22d4476..0000000 --- a/app/src/main/java/org/sopt/and/feature/my/MyState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.sopt.and.feature.my - -data class MyState( - val hobby: String = "" -) diff --git a/app/src/main/java/org/sopt/and/feature/my/MyViewModel.kt b/app/src/main/java/org/sopt/and/feature/my/MyViewModel.kt index abe8514..b054a01 100644 --- a/app/src/main/java/org/sopt/and/feature/my/MyViewModel.kt +++ b/app/src/main/java/org/sopt/and/feature/my/MyViewModel.kt @@ -1,39 +1,75 @@ package org.sopt.and.feature.my -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.sopt.and.R -import org.sopt.and.domain.repository.WavveRepository +import org.sopt.and.core.util.BaseViewModel +import org.sopt.and.core.util.LoadState +import org.sopt.and.domain.repository.MyRepository +import org.sopt.and.domain.usecase.MyUseCase import javax.inject.Inject @HiltViewModel class MyViewModel @Inject constructor( - private val wavveRepository: WavveRepository -) : ViewModel() { - private val _state = MutableStateFlow(MyState()) - val state: StateFlow - get() = _state.asStateFlow() + private val getHobbyUseCase: MyUseCase, + private val MyRepository : MyRepository +) : BaseViewModel() { - private val _sideEffect: MutableSharedFlow = MutableSharedFlow() - val sideEffect: SharedFlow - get() = _sideEffect.asSharedFlow() + override fun createInitialState(): MyContract.MyState = MyContract.MyState() - fun getUserHobby() { + override suspend fun handleEvent(intent: MyContract.MyEvent) { + when (intent) { + is MyContract.MyEvent.FetchMyHobby -> setState { copy(uiState = intent.uiState) } + } + } + + // usecase 사용했을 때, + fun fetchUserHobby() { + viewModelScope.launch { + setIntent( + MyContract.MyEvent.FetchMyHobby(uiState = LoadState.Loading, userInformation = currentState.profile) + ) + getHobbyUseCase().onSuccess { profile-> + setIntent( + MyContract.MyEvent.FetchMyHobby( + uiState = LoadState.Success, + userInformation = profile + ) + ) + }.onFailure { + setIntent( + MyContract.MyEvent.FetchMyHobby( + uiState = LoadState.Failure, + userInformation = currentState.profile + ) + ) + setSideEffect({ MyContract.MySideEffect.ShowToast(R.string.common_failure) }) + } + } + } + + // usecase 사용안했을 때, + fun getUserHobbyRepository() { viewModelScope.launch { - wavveRepository.getHobby().onSuccess { hobbyEntity -> - _state.value = _state.value.copy( - hobby = hobbyEntity.hobby + setIntent( + MyContract.MyEvent.FetchMyHobby(uiState = LoadState.Loading, userInformation = currentState.profile) + ) + MyRepository.getHobby().onSuccess { + setIntent( + MyContract.MyEvent.FetchMyHobby( + uiState = LoadState.Success, + userInformation = currentState.profile + ) ) }.onFailure { - _sideEffect.emit(MySideEffect.ShowToast(R.string.common_failure)) + setIntent( + MyContract.MyEvent.FetchMyHobby( + uiState = LoadState.Failure, + userInformation = currentState.profile + ) + ) + setSideEffect({ MyContract.MySideEffect.ShowToast(R.string.common_failure) }) } } } diff --git a/app/src/main/java/org/sopt/and/component/EventContent.kt b/app/src/main/java/org/sopt/and/feature/my/component/EventContent.kt similarity index 98% rename from app/src/main/java/org/sopt/and/component/EventContent.kt rename to app/src/main/java/org/sopt/and/feature/my/component/EventContent.kt index 2d806da..6c96fb6 100644 --- a/app/src/main/java/org/sopt/and/component/EventContent.kt +++ b/app/src/main/java/org/sopt/and/feature/my/component/EventContent.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.feature.my.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/org/sopt/and/component/HistoryContent.kt b/app/src/main/java/org/sopt/and/feature/my/component/HistoryContent.kt similarity index 98% rename from app/src/main/java/org/sopt/and/component/HistoryContent.kt rename to app/src/main/java/org/sopt/and/feature/my/component/HistoryContent.kt index 485d3e0..e0d3a6c 100644 --- a/app/src/main/java/org/sopt/and/component/HistoryContent.kt +++ b/app/src/main/java/org/sopt/and/feature/my/component/HistoryContent.kt @@ -1,4 +1,4 @@ -package org.sopt.and.component +package org.sopt.and.feature.my.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth diff --git a/app/src/main/java/org/sopt/and/feature/search/SearchNavigation.kt b/app/src/main/java/org/sopt/and/feature/search/SearchNavigation.kt index 594b14d..1a4f53a 100644 --- a/app/src/main/java/org/sopt/and/feature/search/SearchNavigation.kt +++ b/app/src/main/java/org/sopt/and/feature/search/SearchNavigation.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.serialization.Serializable -import org.sopt.and.feature.main.MainTabRoute +import org.sopt.and.core.navigation.MainTabRoute fun NavController.navigateSearch( navOptions: NavOptions diff --git a/app/src/main/java/org/sopt/and/feature/signin/SignInContract.kt b/app/src/main/java/org/sopt/and/feature/signin/SignInContract.kt new file mode 100644 index 0000000..d6d0f10 --- /dev/null +++ b/app/src/main/java/org/sopt/and/feature/signin/SignInContract.kt @@ -0,0 +1,22 @@ +package org.sopt.and.feature.signin + +class SignInContract { + data class SignInState( + val username: String = "", + val password: String = "", + var isPasswordVisible: Boolean = false, + ) { + val isButtonEnabled: Boolean = username.isNotEmpty() && password.isNotEmpty() + } + + sealed class SignInEvent { + data class SetUsername(val username: String) : SignInEvent() + data class SetPassword(val password: String) : SignInEvent() + } + + sealed class SignInSideEffect { + data object NavigateToSignUp : SignInSideEffect() + data object NavigateToHome : SignInSideEffect() + data class ShowSnackBar(val snackBarMessage: String) : SignInSideEffect() + } +} diff --git a/app/src/main/java/org/sopt/and/feature/signin/SignInNavigation.kt b/app/src/main/java/org/sopt/and/feature/signin/SignInNavigation.kt index d827541..c9bb2a5 100644 --- a/app/src/main/java/org/sopt/and/feature/signin/SignInNavigation.kt +++ b/app/src/main/java/org/sopt/and/feature/signin/SignInNavigation.kt @@ -4,7 +4,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import kotlinx.serialization.Serializable -import org.sopt.and.feature.main.MainTabRoute +import org.sopt.and.core.navigation.MainTabRoute fun NavController.navigateSignIn() { navigate(SignIn) diff --git a/app/src/main/java/org/sopt/and/feature/signin/SignInRoute.kt b/app/src/main/java/org/sopt/and/feature/signin/SignInRoute.kt index 8d1bce0..c57f6d5 100644 --- a/app/src/main/java/org/sopt/and/feature/signin/SignInRoute.kt +++ b/app/src/main/java/org/sopt/and/feature/signin/SignInRoute.kt @@ -25,7 +25,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -37,9 +36,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import org.sopt.and.R -import org.sopt.and.component.RoundedButton -import org.sopt.and.component.AuthTextField -import org.sopt.and.component.TopBar +import org.sopt.and.core.component.AuthTextField +import org.sopt.and.core.component.RoundedButton +import org.sopt.and.core.component.TopBar +import org.sopt.and.feature.signin.SignInContract.SignInEvent +import org.sopt.and.feature.signin.SignInContract.SignInSideEffect import org.sopt.and.ui.theme.ANDANDROIDTheme import org.sopt.and.ui.theme.Black import org.sopt.and.ui.theme.LightGray @@ -79,8 +80,12 @@ fun SignInRoute( navigateToSignUp = navigateToSignUp, username = state.username, password = state.password, - onUsernameChange = viewModel::setUsername, - onPasswordChange = viewModel::setPassword, + onUsernameChange = { username -> + viewModel.setEvent(SignInEvent.SetUsername(username = username)) + }, + onPasswordChange = { password -> + viewModel.setEvent(SignInEvent.SetPassword(password = password)) + }, isPasswordVisible = state.isPasswordVisible, reversePasswordVisibility = viewModel::reversePasswordVisibility, isButtonEnabled = state.isButtonEnabled, diff --git a/app/src/main/java/org/sopt/and/feature/signin/SignInSideEffect.kt b/app/src/main/java/org/sopt/and/feature/signin/SignInSideEffect.kt deleted file mode 100644 index 2163076..0000000 --- a/app/src/main/java/org/sopt/and/feature/signin/SignInSideEffect.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.and.feature.signin - -sealed class SignInSideEffect { - data object NavigateToSignUp : SignInSideEffect() - data object NavigateToHome : SignInSideEffect() - data class ShowSnackBar(val snackBarMessage: String) : SignInSideEffect() -} diff --git a/app/src/main/java/org/sopt/and/feature/signin/SignInState.kt b/app/src/main/java/org/sopt/and/feature/signin/SignInState.kt deleted file mode 100644 index eb3319b..0000000 --- a/app/src/main/java/org/sopt/and/feature/signin/SignInState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.and.feature.signin - -data class SignInState( - val username: String = "", - val password: String = "", - var isPasswordVisible: Boolean = false, -) { - val isButtonEnabled: Boolean = username.isNotEmpty() && password.isNotEmpty() -} diff --git a/app/src/main/java/org/sopt/and/feature/signin/SignInViewModel.kt b/app/src/main/java/org/sopt/and/feature/signin/SignInViewModel.kt index d976b19..79980df 100644 --- a/app/src/main/java/org/sopt/and/feature/signin/SignInViewModel.kt +++ b/app/src/main/java/org/sopt/and/feature/signin/SignInViewModel.kt @@ -11,33 +11,54 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.sopt.and.domain.entity.request.RequestSignInEntity -import org.sopt.and.domain.repository.WavveRepository +import org.sopt.and.domain.usecase.SignInUseCase +import org.sopt.and.feature.signin.SignInContract.SignInEvent +import org.sopt.and.feature.signin.SignInContract.SignInSideEffect +import org.sopt.and.feature.signin.SignInContract.SignInState import org.sopt.and.sharedpreference.User import javax.inject.Inject @HiltViewModel class SignInViewModel @Inject constructor( - private val user:User, - private val wavveRepository: WavveRepository + private val user: User, + private val signInUseCase: SignInUseCase ) : ViewModel() { private val _state = MutableStateFlow(SignInState()) val state: StateFlow get() = _state.asStateFlow() + private val currentState: SignInState + get() = state.value private val _sideEffect: MutableSharedFlow = MutableSharedFlow() val sideEffect: SharedFlow get() = _sideEffect.asSharedFlow() - fun setUsername(username: String) { - _state.value = _state.value.copy( - username = username - ) + private fun setState(reduce: SignInState.() -> SignInState) { + _state.value = currentState.reduce() } - fun setPassword(password: String) { - _state.value = _state.value.copy( - password = password - ) + fun setEvent(event: SignInEvent) { + dispatchEvent(event) + } + + private fun dispatchEvent(event: SignInEvent) = viewModelScope.launch { + handleEvent(event) + } + + private fun handleEvent(event: SignInEvent) { + when (event) { + is SignInEvent.SetUsername -> { + setState { + copy(username = event.username) + } + } + + is SignInEvent.SetPassword -> { + setState { + copy(password = event.password) + } + } + } } fun reversePasswordVisibility() { @@ -49,7 +70,7 @@ class SignInViewModel @Inject constructor( fun isSignInValid() { viewModelScope.launch { var toastMessage: String = "" - wavveRepository.signIn( + signInUseCase.invoke( RequestSignInEntity( username = _state.value.username, password = _state.value.password diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpContract.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpContract.kt new file mode 100644 index 0000000..cd09789 --- /dev/null +++ b/app/src/main/java/org/sopt/and/feature/signup/SignUpContract.kt @@ -0,0 +1,23 @@ +package org.sopt.and.feature.signup + +class SignUpContract { + data class SignUpState( + val username: String = "", + val password: String = "", + val hobby: String = "", + var isPasswordVisible: Boolean = false, + ) { + val isButtonEnabled: Boolean = username.isNotEmpty() && password.isNotEmpty() + } + + sealed class SignUpEvent { + data class SetUsername(val username: String) : SignUpEvent() + data class SetPassword(val password: String) : SignUpEvent() + data class SetHobby(val hobby: String) : SignUpEvent() + } + + sealed class SignUpSideEffect { + data object NavigateToSignIn : SignUpSideEffect() + data class ShowToast(val toastMessage: Int) : SignUpSideEffect() + } +} diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpNavigation.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpNavigation.kt index a97bcca..b473226 100644 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpNavigation.kt +++ b/app/src/main/java/org/sopt/and/feature/signup/SignUpNavigation.kt @@ -4,7 +4,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import kotlinx.serialization.Serializable -import org.sopt.and.feature.main.MainTabRoute +import org.sopt.and.core.navigation.MainTabRoute fun NavController.navigateSignUp() { navigate(SignUp) diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpRoute.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpRoute.kt index f2a7271..06c1b98 100644 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpRoute.kt +++ b/app/src/main/java/org/sopt/and/feature/signup/SignUpRoute.kt @@ -31,10 +31,12 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import org.sopt.and.R -import org.sopt.and.component.ExpandedButton -import org.sopt.and.component.AuthTextField -import org.sopt.and.component.TopBar -import org.sopt.and.showToast +import org.sopt.and.core.component.ExpandedButton +import org.sopt.and.core.component.AuthTextField +import org.sopt.and.core.component.TopBar +import org.sopt.and.core.extension.showToast +import org.sopt.and.feature.signup.SignUpContract.SignUpEvent +import org.sopt.and.feature.signup.SignUpContract.SignUpSideEffect import org.sopt.and.ui.theme.ANDANDROIDTheme import org.sopt.and.ui.theme.Black import org.sopt.and.ui.theme.LightGray @@ -70,10 +72,16 @@ fun SignUpRoute( username = state.username, password = state.password, hobby = state.hobby, - onUsernameChange = viewModel::setUsername, - onPasswordChange = viewModel::setPassword, + onUsernameChange = { username -> + viewModel.setEvent(SignUpEvent.SetUsername(username = username)) + }, + onPasswordChange = { password -> + viewModel.setEvent(SignUpEvent.SetPassword(password = password)) + }, isPasswordVisible = state.isPasswordVisible, - onHobbyChange = viewModel::setHobby, + onHobbyChange = { hobby -> + viewModel.setEvent(SignUpEvent.SetHobby(hobby = hobby)) + }, reversePasswordVisibility = viewModel::reversePasswordVisibility, isButtonEnabled = state.isButtonEnabled, checkSignUpValidation = viewModel::isSignUpValid, diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpSideEffect.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpSideEffect.kt deleted file mode 100644 index 5c1deb0..0000000 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpSideEffect.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.and.feature.signup - -sealed class SignUpSideEffect { - data object NavigateToSignIn : SignUpSideEffect() - data class ShowToast(val toastMessage: Int) : SignUpSideEffect() -} diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpState.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpState.kt deleted file mode 100644 index 0184f88..0000000 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.and.feature.signup - -data class SignUpState( - val username: String = "", - val password: String = "", - val hobby: String = "", - var isPasswordVisible: Boolean = false, -) { - val isButtonEnabled: Boolean = username.isNotEmpty() && password.isNotEmpty() -} diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt index 09c27d2..aec4598 100644 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt @@ -12,45 +12,64 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.sopt.and.R import org.sopt.and.domain.entity.request.RequestSignUpEntity -import org.sopt.and.domain.repository.WavveRepository +import org.sopt.and.domain.usecase.SignUpUseCase +import org.sopt.and.feature.signup.SignUpContract.SignUpEvent +import org.sopt.and.feature.signup.SignUpContract.SignUpSideEffect import java.util.regex.Pattern import javax.inject.Inject @HiltViewModel class SignUpViewModel @Inject constructor( - private val wavveRepository: WavveRepository + private val signUpUseCase: SignUpUseCase ) : ViewModel() { - private val _state = MutableStateFlow(SignUpState()) - val state: StateFlow + private val _state = MutableStateFlow(SignUpContract.SignUpState()) + val state: StateFlow get() = _state.asStateFlow() + private val currentState: SignUpContract.SignUpState + get() = state.value private val _sideEffect: MutableSharedFlow = MutableSharedFlow() val sideEffect: SharedFlow get() = _sideEffect.asSharedFlow() - fun setUsername(username: String) { - _state.value = _state.value.copy( - username = username - ) + private fun setState(reduce: SignUpContract.SignUpState.() -> SignUpContract.SignUpState) { + _state.value = currentState.reduce() } - fun setPassword(password: String) { - _state.value = _state.value.copy( - password = password - ) + fun setEvent(event: SignUpEvent) { + dispatchEvent(event) } - fun setHobby(hobby: String) { - _state.value = _state.value.copy( - hobby = hobby - ) + private fun dispatchEvent(event: SignUpEvent) = viewModelScope.launch { + handleEvent(event) + } + + private fun handleEvent(event: SignUpEvent) { + when (event) { + is SignUpEvent.SetUsername -> { + setState { + copy(username = event.username) + } + } + + is SignUpEvent.SetPassword -> { + setState { + copy(password = event.password) + } + } + is SignUpEvent.SetHobby -> { + setState { + copy(hobby = event.hobby) + } + } + } } fun isSignUpValid() { viewModelScope.launch { if (isUsernameValid() && isPasswordValid() && isHobbyValid()) { - wavveRepository.signUp( + signUpUseCase.invoke( RequestSignUpEntity( username = _state.value.username, password = _state.value.password, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4386653..6c71402 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ ksp = "2.0.0-1.0.22" okhttp = "4.11.0" retrofit = "2.9.0" retrofitKotlinSerializationConverter = "1.0.0" +timber = "5.0.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -46,6 +47,7 @@ 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" } +timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }