-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[UI/#14] 회원가입 뷰 / UI 구현 #46
Changes from 17 commits
731883d
ecd1f10
d101e4e
bc3e526
89405e7
b2671ce
e6f35ef
b01077a
6372c07
c2d1b25
f9dbc60
fc58c97
0106f8e
e7b273a
ee0ca25
f925bb4
02e7a05
39ec9e6
cb3b7a0
0b584c1
af144e7
4ab493f
0c69b82
e7d162b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,17 @@ | ||
package com.terning.feature.onboarding.filtering | ||
|
||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import com.terning.feature.onboarding.filtering.navigation.FilteringNavigation | ||
import androidx.navigation.NavController | ||
|
||
@Composable | ||
fun FilteringRoute(){ | ||
fun FilteringRoute( | ||
navController: NavController | ||
) { | ||
FilteringScreen() | ||
} | ||
|
||
} | ||
@Composable | ||
fun FilteringScreen() { | ||
Text(text = "filtering") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,30 @@ | ||
package com.terning.feature.onboarding.filtering.navigation | ||
|
||
class FilteringNavigation { | ||
} | ||
import androidx.navigation.NavController | ||
import androidx.navigation.NavGraphBuilder | ||
import androidx.navigation.NavHostController | ||
import androidx.navigation.NavOptions | ||
import androidx.navigation.compose.composable | ||
import com.terning.core.navigation.Route | ||
import com.terning.feature.onboarding.filtering.FilteringRoute | ||
import kotlinx.serialization.Serializable | ||
|
||
fun NavController.navigateFiltering(navOptions: NavOptions? = null) { | ||
navigate( | ||
route = Filtering, | ||
navOptions = navOptions | ||
) | ||
} | ||
|
||
fun NavGraphBuilder.filteringNavGraph( | ||
navHostController: NavHostController | ||
) { | ||
composable<Filtering> { | ||
FilteringRoute( | ||
navController = navHostController | ||
) | ||
} | ||
} | ||
|
||
@Serializable | ||
data object Filtering : Route |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,126 @@ | ||
package com.terning.feature.onboarding.signup | ||
|
||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalFocusManager | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.unit.dp | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
import androidx.navigation.NavController | ||
import com.terning.core.designsystem.component.button.RectangleButton | ||
import com.terning.core.designsystem.component.textfield.NameTextField | ||
import com.terning.core.designsystem.theme.TerningTheme | ||
import com.terning.core.extension.addFocusCleaner | ||
import com.terning.core.extension.noRippleClickable | ||
import com.terning.feature.R | ||
import com.terning.feature.onboarding.filtering.navigation.navigateFiltering | ||
import com.terning.feature.onboarding.signup.component.SignUpBottomSheet | ||
import com.terning.feature.onboarding.signup.component.SignUpProfile | ||
|
||
@Composable | ||
fun SignUpRoute() { | ||
SignUpScreen() | ||
fun SignUpRoute( | ||
signUpViewModel: SignUpViewModel = hiltViewModel(), | ||
navController: NavController | ||
) { | ||
val signUpState by signUpViewModel.state.collectAsStateWithLifecycle() | ||
|
||
SignUpScreen( | ||
signUpViewModel = signUpViewModel, | ||
signUpState = signUpState, | ||
onButtonClick = { navController.navigateFiltering() } | ||
) | ||
} | ||
|
||
@Composable | ||
fun SignUpScreen() { | ||
fun SignUpScreen( | ||
modifier: Modifier = Modifier, | ||
signUpState: SignUpState, | ||
signUpViewModel: SignUpViewModel, | ||
onButtonClick: () -> Unit | ||
) { | ||
val focusManager = LocalFocusManager.current | ||
var showBottomSheet by remember { mutableStateOf(false) } | ||
|
||
} | ||
Column( | ||
modifier = modifier | ||
.fillMaxSize() | ||
.addFocusCleaner(focusManager) | ||
) { | ||
Spacer(modifier = modifier.weight(1f)) | ||
Text( | ||
text = stringResource(id = R.string.sign_up_title), | ||
style = TerningTheme.typography.heading2, | ||
modifier = modifier.padding( | ||
bottom = 42.dp, | ||
start = 24.dp | ||
) | ||
) | ||
if (showBottomSheet) { | ||
SignUpBottomSheet( | ||
onDismiss = { showBottomSheet = false }, | ||
onSaveClick = { showBottomSheet = false } | ||
) | ||
} | ||
Text( | ||
text = stringResource(id = R.string.sign_up_profile_image), | ||
style = TerningTheme.typography.body2, | ||
modifier = modifier | ||
.padding( | ||
start = 24.dp, | ||
bottom = 20.dp | ||
) | ||
) | ||
Column( | ||
modifier = modifier | ||
.align(Alignment.CenterHorizontally) | ||
.padding(bottom = 52.dp) | ||
) { | ||
SignUpProfile( | ||
modifier = modifier.noRippleClickable { | ||
showBottomSheet = true | ||
} | ||
) | ||
} | ||
Column( | ||
modifier = modifier | ||
.align(Alignment.CenterHorizontally) | ||
.padding(horizontal = 24.dp) | ||
) { | ||
Text( | ||
text = stringResource(id = R.string.sign_up_name), | ||
modifier = modifier.padding(bottom = 20.dp) | ||
) | ||
NameTextField( | ||
value = signUpState.name, | ||
onValueChange = { name -> | ||
signUpViewModel.isInputValid(name) | ||
Comment on lines
+105
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 뷰모델 네이밍 방식 통일해야 할 것 같은데 얘기해보면 좋을 것 같습니당!~!~~! ~~~! |
||
}, | ||
hint = stringResource(id = R.string.sign_up_hint), | ||
drawLineColor = signUpState.drawLineColor, | ||
helperMessage = signUpState.helper, | ||
helperIcon = signUpState.helperIcon, | ||
helperColor = signUpState.helperColor | ||
) | ||
} | ||
Spacer(modifier = modifier.weight(5f)) | ||
RectangleButton( | ||
style = TerningTheme.typography.button1, | ||
paddingVertical = 20.dp, | ||
text = R.string.sign_up_next_button, | ||
onButtonClick = { onButtonClick() }, | ||
modifier = modifier.padding(bottom = 12.dp), | ||
isEnabled = signUpState.isButtonValid | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.terning.feature.onboarding.signup | ||
|
||
import androidx.compose.ui.graphics.Color | ||
import com.terning.core.designsystem.theme.Grey400 | ||
import com.terning.core.designsystem.theme.Grey500 | ||
import com.terning.feature.onboarding.signup.SignUpViewModel.Companion.HELPER | ||
|
||
data class SignUpState( | ||
val name: String = "", | ||
val character: Int = 0, | ||
val drawLineColor: Color = Grey500, | ||
val helper: String = HELPER, | ||
val helperIcon: Int? = null, | ||
val helperColor: Color = Grey400, | ||
val isButtonValid : Boolean = false | ||
) |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 나중에 리컴포지션에 대한 리팩토링을 하게 된다면, 뷰모델에서 텍스트를 관리하는 것보단 스크린 자체에서 관리를 하는 방향의 수정을 시도해봐도 좋을 것 같아요!! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,62 @@ | ||
package com.terning.feature.onboarding.signup | ||
|
||
import androidx.lifecycle.ViewModel | ||
import com.terning.core.designsystem.theme.Grey400 | ||
import com.terning.core.designsystem.theme.Grey500 | ||
import com.terning.core.designsystem.theme.TerningMain | ||
import com.terning.core.designsystem.theme.WarningRed | ||
import com.terning.feature.R | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class SignUpViewModel @Inject constructor() : ViewModel(){ | ||
class SignUpViewModel @Inject constructor() : ViewModel() { | ||
|
||
} | ||
private val _state: MutableStateFlow<SignUpState> = MutableStateFlow(SignUpState()) | ||
val state: StateFlow<SignUpState> get() = _state.asStateFlow() | ||
|
||
fun isInputValid(name: String) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regex!! 새롭게 알아갑니다ㅎㅎㅎㅎ |
||
val nameErrorRegex = Regex(NAME_ERROR) | ||
val trimmedName = if (name.length > MAX_LENGTH) name.substring(0, MAX_LENGTH) else name | ||
|
||
when { | ||
nameErrorRegex.containsMatchIn(trimmedName) -> _state.value = _state.value.copy( | ||
name = trimmedName, | ||
drawLineColor = WarningRed, | ||
helper = HELPER_ERROR, | ||
helperIcon = R.drawable.ic_sign_up_error, | ||
helperColor = WarningRed, | ||
isButtonValid = false | ||
) | ||
|
||
trimmedName.isEmpty() -> _state.value = _state.value.copy( | ||
name = trimmedName, | ||
drawLineColor = Grey500, | ||
helper = HELPER, | ||
helperIcon = null, | ||
helperColor = Grey400, | ||
isButtonValid = false | ||
) | ||
|
||
else -> _state.value = _state.value.copy( | ||
name = trimmedName, | ||
drawLineColor = TerningMain, | ||
helper = HELPER_AVAILABLE, | ||
helperIcon = R.drawable.ic_sign_up_available, | ||
helperColor = TerningMain, | ||
isButtonValid = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 한번에 관리하니까 직관적이네요 ㅜ ㅜ 🥹 |
||
) | ||
} | ||
} | ||
|
||
companion object { | ||
const val NAME_ERROR = "[!@#\$%^&*(),.?\":{}|<>\\[\\]\\\\/]" | ||
const val HELPER = "12자리 이내, 문자/숫자 가능, 특수문자/기호 입력불가" | ||
private const val MAX_LENGTH = 12 | ||
private const val HELPER_ERROR = "이름에 특수문자는 입력할 수 없어요" | ||
private const val HELPER_AVAILABLE = "이용 가능한 이름이에요" | ||
} | ||
Comment on lines
+55
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기도 string으로 빼는게 가능할까요?? 가능하다면 string 분리하는게 더 좋을 것 같아서요...! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 그렇게 생각하는데 뷰모델에서 제대로 추출이 안 돼서,, 추후에 더 해보겠습니당 |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SignUpScreen이 아니라 SignUpRoute에서 뷰모델을 생성해서 넘기는 이유가 있나용!?? .....진짜모름
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 화면을 그리는 UI하고 다른 로직을 적는 걸 분리하고 싶어서 SignUpRoute에서 생성했었습니다!
나중에 성공했을 때의 로직만 screen으로 넘기도록 리팩해볼게요!