Skip to content

Commit

Permalink
refactor #11 - SignUpScreen MVI 리팩토링
Browse files Browse the repository at this point in the history
  • Loading branch information
sayyyho committed Dec 17, 2024
1 parent c7c16cf commit 1b2f1c7
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 167 deletions.
14 changes: 14 additions & 0 deletions app/src/main/java/org/sopt/and/core/utils/extension/Modifier.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.and.core.utils.extension

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed

fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed {
this.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sopt.and.domain.entity

data class UserRegisterResult(
val no: Int?
)
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
package org.sopt.and.domain.repository

import org.sopt.and.data.datasource.SignUpDataSource
import org.sopt.and.data.repositoryimpl.SignUpRepositoryImpl
import org.sopt.and.data.service.ServicePool
import org.sopt.and.domain.model.SignUpInformationEntity
import org.sopt.and.domain.model.SignUpResponseEntity
import org.sopt.and.domain.entity.BaseResult
import org.sopt.and.domain.entity.UserData
import org.sopt.and.domain.entity.UserRegisterResult

interface SignUpRepository {
suspend fun signUp(request: SignUpInformationEntity): Result<SignUpResponseEntity>

companion object {
fun create(): SignUpRepositoryImpl {
return SignUpRepositoryImpl(
signUpDataSource = SignUpDataSource(
userService = ServicePool.userService
)
)
}
}
interface SignUpRepository {
suspend fun registerUser(user : UserData): BaseResult<UserRegisterResult>
}
15 changes: 9 additions & 6 deletions app/src/main/java/org/sopt/and/domain/usecase/SignUpUseCase.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package org.sopt.and.domain.usecase

import org.sopt.and.domain.model.SignUpInformationEntity
import org.sopt.and.domain.model.SignUpResponseEntity
import org.sopt.and.domain.entity.BaseResult
import org.sopt.and.domain.entity.UserData
import org.sopt.and.domain.entity.UserRegisterResult
import org.sopt.and.domain.repository.SignUpRepository
import javax.inject.Inject

class SignUpUseCase(
private val signUpRepository: SignUpRepository
class SignUpUseCase @Inject constructor(
private val userRegisterRepository: SignUpRepository
) {
suspend operator fun invoke(request: SignUpInformationEntity): Result<SignUpResponseEntity> =
signUpRepository.signUp(request = request)
suspend operator fun invoke(user: UserData): BaseResult<UserRegisterResult> {
return userRegisterRepository.registerUser(user)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.sopt.and.presentation.auth.signup

import org.sopt.and.presentation.util.UiEffect
import org.sopt.and.presentation.util.UiEvent
import org.sopt.and.presentation.util.UiState

class SignUpContract {
data class SignUpUiState(
val username: String = "",
val password: String = "",
val hobby: String = "",
val isUserNameValid: Boolean = false,
val isPasswordValid: Boolean = false,
val isHobbyValid: Boolean = false,
val isUserNameFieldFocused: Boolean = false,
val isPasswordFieldFocused: Boolean = false,
val isHobbyFieldFocused: Boolean = false,
val isValid: Boolean = false,
val isLoading: Boolean = false,
val errorMessage: String? = null
) : UiState

sealed class SignUpUiEvent : UiEvent {
data class UpdateUserName(val username: String) : SignUpUiEvent()
data class UpdatePassword(val password: String) : SignUpUiEvent()
data class UpdateHobby(val hobby: String) : SignUpUiEvent()
data class UpdateFieldFocus(val field: Field, val isFocused: Boolean) : SignUpUiEvent()
data object SignUpFormSubmit : SignUpUiEvent()
data object Close : SignUpUiEvent()
}

sealed class SignUpUiEffect : UiEffect {
data object ShowSuccessToast : SignUpUiEffect()
data class ShowErrorToast(val message: String) : SignUpUiEffect()
data object NavigateToSignIn : SignUpUiEffect()
data object NavigateUp : SignUpUiEffect()
}

enum class Field {
UserName, Password, Hobby
}
}
117 changes: 74 additions & 43 deletions app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt
Original file line number Diff line number Diff line change
@@ -1,43 +1,71 @@
package org.sopt.and.presentation.signup

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Scaffold
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import org.sopt.and.R
import org.sopt.and.core.utils.extension.noRippleClickable
import org.sopt.and.presentation.auth.signup.SignUpContract
import org.sopt.and.presentation.auth.signup.viewmodel.SignUpViewModel
import org.sopt.and.presentation.components.SnSBox
import org.sopt.and.presentation.signup.components.SignUpButton
import org.sopt.and.presentation.signup.components.SignUpGreetingText
import org.sopt.and.presentation.signup.components.SignUpHobbyField
import org.sopt.and.presentation.signup.components.SignUpPasswordField
import org.sopt.and.presentation.signup.components.SignUpTopBar
import org.sopt.and.presentation.signup.components.SignUpUsernameField
import org.sopt.and.presentation.viewmodelfactory.SignUpViewModelFactory
import org.sopt.and.ui.theme.ANDANDROIDTheme
import org.sopt.and.presentation.util.Utils.showToast
import org.sopt.and.ui.theme.Black100
import org.sopt.and.ui.theme.Blue100
import org.sopt.and.ui.theme.WavveDisabled
import org.sopt.and.ui.theme.White100

@Composable
fun SignUpScreen(
modifier: Modifier = Modifier,
navigateToSignIn: () -> Unit,
navigateUp: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SignUpViewModel = hiltViewModel()
) {
val signUpViewModel: SignUpViewModel = viewModel(
factory = SignUpViewModelFactory()
)
val signUpUiState by signUpViewModel.uiState.collectAsStateWithLifecycle()

val signUpState by viewModel.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current

LaunchedEffect(Unit) {
viewModel.effect.collect { effect ->
when (effect) {
is SignUpContract.SignUpUiEffect.ShowSuccessToast -> {
context.showToast(context.getString(R.string.sign_up_toast_success))
navigateToSignIn()
}

is SignUpContract.SignUpUiEffect.ShowErrorToast -> {
context.showToast(effect.message)
}

is SignUpContract.SignUpUiEffect.NavigateToSignIn -> navigateToSignIn()
is SignUpContract.SignUpUiEffect.NavigateUp -> navigateUp()
}
}
}
Column(
modifier = modifier
.fillMaxSize()
Expand All @@ -58,56 +86,59 @@ fun SignUpScreen(
Spacer(modifier = Modifier.height(20.dp))

SignUpUsernameField(
signUpUsername = signUpUiState.signUpUsername,
onSignUpUsernameChange = signUpViewModel::setSignUpUsername
signUpUsername = signUpState.username,
onSignUpUsernameChange = {
viewModel.sendEvent(
SignUpContract.SignUpUiEvent.UpdateUserName(
it
)
)
}
)

Spacer(modifier = Modifier.height(20.dp))

SignUpPasswordField(
signUpPassword = signUpUiState.signUpPassword,
onSignUpPasswordChange = signUpViewModel::setSignUpPassword,
isSignUpPasswordVisible = signUpUiState.isSignUpPasswordVisible,
onVisibilityChange = signUpViewModel::changeSignUpPasswordVisibility
signUpPassword = signUpState.password,
onSignUpPasswordChange = {
viewModel.sendEvent(
SignUpContract.SignUpUiEvent.UpdatePassword(
it
)
)
}
)

Spacer(modifier = Modifier.height(20.dp))


SignUpHobbyField(
signUpHobby = signUpUiState.signUpHobby,
onSignUpHobbyChange = signUpViewModel::setSignUpHobby
signUpHobby = signUpState.hobby,
onSignUpHobbyChange = {
viewModel.sendEvent(
SignUpContract.SignUpUiEvent.UpdateHobby(it)
)
}
)

Spacer(modifier = Modifier.size(40.dp))

SnSBox(stringResource(R.string.sign_in_link_with_another_service_title))
}

SignUpButton (
signUpUsername = signUpUiState.signUpUsername,
signUpPassword = signUpUiState.signUpPassword,
signUpHobby = signUpUiState.signUpHobby,
onSignUpComplete = navigateToSignIn,
signUpViewModel = signUpViewModel
)
}
}

@Preview(
showBackground = true,
showSystemUi = true
)
@Composable
fun SignUpScreenPreview() {
ANDANDROIDTheme {
Scaffold(
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
) { innerPadding ->
SignUpScreen(
modifier = Modifier
.padding(innerPadding),
navigateToSignIn = { }
.fillMaxWidth()
.background(
color = if (signUpState.isValid) Blue100 else WavveDisabled
)
.wrapContentHeight()
.noRippleClickable { viewModel.sendEvent(SignUpContract.SignUpUiEvent.SignUpFormSubmit) }
.padding(vertical = 14.dp)
) {
Text(
text = stringResource(R.string.sign_up_text_wavve_sign_up),
color = White100
)
}
}
Expand Down
Loading

0 comments on commit 1b2f1c7

Please sign in to comment.