diff --git a/app/src/main/java/com/sopt/clody/presentation/di/LanguageModule.kt b/app/src/main/java/com/sopt/clody/presentation/di/LanguageModule.kt new file mode 100644 index 00000000..81d46845 --- /dev/null +++ b/app/src/main/java/com/sopt/clody/presentation/di/LanguageModule.kt @@ -0,0 +1,18 @@ +package com.sopt.clody.presentation.di + +import com.sopt.clody.presentation.utils.language.LanguageProvider +import com.sopt.clody.presentation.utils.language.LanguageProviderImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface LanguageModule { + + @Binds + fun bindLanguageProvider( + languageProviderImpl: LanguageProviderImpl, + ): LanguageProvider +} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/component/textfield/NickNameTextField.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/component/textfield/NickNameTextField.kt index a688900f..711fff7d 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/component/textfield/NickNameTextField.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/component/textfield/NickNameTextField.kt @@ -29,6 +29,7 @@ import com.sopt.clody.ui.theme.ClodyTheme fun NickNameTextField( value: String, onValueChange: (String) -> Unit, + maxLength: Int, isFocused: Boolean, isValid: Boolean, onRemove: () -> Unit, @@ -36,8 +37,6 @@ fun NickNameTextField( modifier: Modifier = Modifier, hint: String = "", ) { - val maxLength = 10 - BasicTextField( value = value, onValueChange = { @@ -102,6 +101,7 @@ fun PreviewNickNameTextField() { NickNameTextField( value = "닉네임", onValueChange = {}, + maxLength = 15, isFocused = false, isValid = true, onRemove = {}, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt index 501a0140..746f35e7 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt @@ -9,11 +9,14 @@ class SignUpContract { val nickname: String = "", val isNicknameFocused: Boolean = false, val isValidNickname: Boolean = true, + val nicknameMaxLength: Int = 15, val nicknameMessage: String = DEFAULT_NICKNAME_MESSAGE, val isLoading: Boolean = false, val errorMessage: String? = null, val serviceChecked: Boolean = false, + val serviceUrl: String = "", val privacyChecked: Boolean = false, + val privacyUrl: String = "", ) : MavericksState { val allChecked: Boolean get() = serviceChecked && privacyChecked @@ -26,6 +29,7 @@ class SignUpContract { sealed class SignUpIntent { data class SetNickname(val value: String) : SignUpIntent() data class SetNicknameFocus(val isFocused: Boolean) : SignUpIntent() + data object SetNicknameMaxLength : SignUpIntent() data object ProceedTerms : SignUpIntent() data class CompleteSignUp(val context: Context) : SignUpIntent() data object ClearError : SignUpIntent() @@ -33,6 +37,7 @@ class SignUpContract { data class ToggleAllChecked(val checked: Boolean) : SignUpIntent() data class ToggleServiceChecked(val checked: Boolean) : SignUpIntent() data class TogglePrivacyChecked(val checked: Boolean) : SignUpIntent() + data object SetWebViewUrl : SignUpIntent() data class OpenWebView(val url: String) : SignUpIntent() data object BackToTerms : SignUpIntent() } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt index fb970fd3..0e744aa3 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt @@ -68,14 +68,14 @@ fun SignUpScreen( allChecked = state.allChecked, serviceChecked = state.serviceChecked, privacyChecked = state.privacyChecked, + serviceUrl = state.serviceUrl, + privacyUrl = state.privacyUrl, onToggleAll = { onIntent(SignUpContract.SignUpIntent.ToggleAllChecked(it)) }, onToggleService = { onIntent(SignUpContract.SignUpIntent.ToggleServiceChecked(it)) }, onTogglePrivacy = { onIntent(SignUpContract.SignUpIntent.TogglePrivacyChecked(it)) }, onAgreeClick = { onIntent(SignUpContract.SignUpIntent.ProceedTerms) }, navigateToPrevious = navigateToPrevious, - navigateToWebView = { url -> - onIntent(SignUpContract.SignUpIntent.OpenWebView(url)) - }, + navigateToWebView = { url -> onIntent(SignUpContract.SignUpIntent.OpenWebView(url)) }, ) } @@ -87,6 +87,7 @@ fun SignUpScreen( onBackClick = { onIntent(SignUpContract.SignUpIntent.BackToTerms) }, isLoading = state.isLoading, isValidNickname = state.isValidNickname, + nicknameMaxLength = state.nicknameMaxLength, nicknameMessage = state.nicknameMessage, isFocused = state.isNicknameFocused, onFocusChanged = { onIntent(SignUpContract.SignUpIntent.SetNicknameFocus(it)) }, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt index feeffc5b..56c63c60 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt @@ -13,6 +13,8 @@ import com.sopt.clody.data.remote.util.NetworkUtil import com.sopt.clody.domain.repository.AuthRepository import com.sopt.clody.domain.repository.TokenRepository import com.sopt.clody.presentation.ui.auth.signup.SignUpContract.Companion.DEFAULT_NICKNAME_MESSAGE +import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls +import com.sopt.clody.presentation.utils.language.LanguageProvider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -30,6 +32,7 @@ class SignUpViewModel @AssistedInject constructor( private val tokenRepository: TokenRepository, private val fcmTokenProvider: FcmTokenProvider, private val networkUtil: NetworkUtil, + private val languageProvider: LanguageProvider, ) : MavericksViewModel(initialState) { private val _intents = Channel(BUFFERED) @@ -41,6 +44,8 @@ class SignUpViewModel @AssistedInject constructor( .receiveAsFlow() .onEach(::handleIntent) .launchIn(viewModelScope) + postIntent(SignUpContract.SignUpIntent.SetNicknameMaxLength) + postIntent(SignUpContract.SignUpIntent.SetWebViewUrl) } fun postIntent(intent: SignUpContract.SignUpIntent) { @@ -51,12 +56,14 @@ class SignUpViewModel @AssistedInject constructor( when (intent) { is SignUpContract.SignUpIntent.SetNickname -> handleSetNickname(intent) is SignUpContract.SignUpIntent.SetNicknameFocus -> handleSetNicknameFocus(intent) + is SignUpContract.SignUpIntent.SetNicknameMaxLength -> setNicknameMaxLength() is SignUpContract.SignUpIntent.ProceedTerms -> handleProceedTerms() is SignUpContract.SignUpIntent.CompleteSignUp -> signUp(intent.context) is SignUpContract.SignUpIntent.ClearError -> clearError() is SignUpContract.SignUpIntent.ToggleAllChecked -> handleToggleAllChecked(intent) is SignUpContract.SignUpIntent.ToggleServiceChecked -> handleToggleServiceChecked(intent) is SignUpContract.SignUpIntent.TogglePrivacyChecked -> handleTogglePrivacyChecked(intent) + is SignUpContract.SignUpIntent.SetWebViewUrl -> setWebViewUrl() is SignUpContract.SignUpIntent.OpenWebView -> handleOpenWebView(intent.url) SignUpContract.SignUpIntent.BackToTerms -> handleBackToTerms() } @@ -81,6 +88,10 @@ class SignUpViewModel @AssistedInject constructor( setState { copy(isNicknameFocused = intent.isFocused) } } + private fun setNicknameMaxLength() { + setState { copy(nicknameMaxLength = languageProvider.getNicknameMaxLength()) } + } + private fun handleProceedTerms() { setState { copy(currentStep = SignUpContract.SignUpState.Step.NICKNAME) } } @@ -103,6 +114,15 @@ class SignUpViewModel @AssistedInject constructor( setState { copy(privacyChecked = intent.checked) } } + private fun setWebViewUrl() { + setState { + copy( + serviceUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.TERMS_OF_SERVICE_URL), + privacyUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.PRIVACY_POLICY_URL), + ) + } + } + private suspend fun handleOpenWebView(url: String) { _sideEffects.send(SignUpContract.SignUpSideEffect.NavigateToWebView(url)) } @@ -153,7 +173,9 @@ class SignUpViewModel @AssistedInject constructor( } private fun validateNickname(nickname: String): Boolean { - val regex = "^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,10}$".toRegex() + val state = withState(this@SignUpViewModel) { it } + setState { copy(nicknameMaxLength = languageProvider.getNicknameMaxLength()) } + val regex = "^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,${state.nicknameMaxLength}$".toRegex() return nickname.matches(regex) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt index e1f2f9c0..50a591b9 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt @@ -38,6 +38,7 @@ import com.sopt.clody.ui.theme.ClodyTheme fun NickNamePage( nickname: String, isValidNickname: Boolean, + nicknameMaxLength: Int, nicknameMessage: String, isLoading: Boolean, isFocused: Boolean, @@ -56,7 +57,7 @@ fun NickNamePage( append(" / ") } withStyle(style = SpanStyle(color = ClodyTheme.colors.gray06)) { - append("10") + append("$nicknameMaxLength") } } @@ -104,6 +105,7 @@ fun NickNamePage( NickNameTextField( value = nickname, onValueChange = onNicknameChange, + maxLength = nicknameMaxLength, hint = stringResource(R.string.nickname_input_hint), isFocused = isFocused, isValid = isValidNickname, @@ -146,6 +148,7 @@ private fun NicknamePagePreview() { NickNamePage( nickname = "클로디", isValidNickname = true, + nicknameMaxLength = 15, nicknameMessage = "사용 가능한 닉네임입니다.", isLoading = false, isFocused = false, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/TermsOfServicePage.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/TermsOfServicePage.kt index df40e950..6b2dd169 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/TermsOfServicePage.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/TermsOfServicePage.kt @@ -26,18 +26,18 @@ import com.sopt.clody.R import com.sopt.clody.presentation.ui.auth.component.checkbox.CustomCheckbox import com.sopt.clody.presentation.ui.component.button.ClodyButton import com.sopt.clody.presentation.ui.home.calendar.component.HorizontalDivider -import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls import com.sopt.clody.presentation.utils.base.BasePreview import com.sopt.clody.presentation.utils.base.ClodyPreview import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage import com.sopt.clody.ui.theme.ClodyTheme -import java.util.Locale @Composable fun TermsOfServicePage( allChecked: Boolean, serviceChecked: Boolean, privacyChecked: Boolean, + serviceUrl: String, + privacyUrl: String, onToggleAll: (Boolean) -> Unit, onToggleService: (Boolean) -> Unit, onTogglePrivacy: (Boolean) -> Unit, @@ -46,10 +46,6 @@ fun TermsOfServicePage( navigateToWebView: (String) -> Unit, ) { val isAgreeButtonEnabled = serviceChecked && privacyChecked - val currentLang = Locale.getDefault().language - - val termsOfService = if (currentLang == "ko") SettingOptionUrls.TERMS_OF_SERVICE_URL.krUrl else SettingOptionUrls.TERMS_OF_SERVICE_URL.enUrl - val privacyPolicy = if (currentLang == "ko") SettingOptionUrls.PRIVACY_POLICY_URL.krUrl else SettingOptionUrls.PRIVACY_POLICY_URL.enUrl Scaffold( topBar = { @@ -117,14 +113,14 @@ fun TermsOfServicePage( text = stringResource(R.string.terms_service_use), checked = serviceChecked, onCheckedChange = onToggleService, - onClickMore = { navigateToWebView(termsOfService) }, + onClickMore = { navigateToWebView(serviceUrl) }, ) Spacer(modifier = Modifier.height(24.dp)) TermsCheckboxRow( text = stringResource(R.string.terms_service_privacy), checked = privacyChecked, onCheckedChange = onTogglePrivacy, - onClickMore = { navigateToWebView(privacyPolicy) }, + onClickMore = { navigateToWebView(privacyUrl) }, ) } }, @@ -169,6 +165,8 @@ private fun TermsOfServicePagePreview() { allChecked = false, serviceChecked = false, privacyChecked = false, + serviceUrl = "", + privacyUrl = "", onToggleAll = {}, onToggleService = {}, onTogglePrivacy = {}, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginContract.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginContract.kt index 4e7d228d..1a9da0a9 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginContract.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginContract.kt @@ -6,11 +6,13 @@ import com.airbnb.mvrx.MavericksState class LoginContract { data class LoginState( + val loginType: LoginType = LoginType.GOOGLE, val isLoading: Boolean = false, val errorMessage: String? = null, ) : MavericksState sealed class LoginIntent { + data object SetLoginType : LoginIntent() data class LoginWithKakao(val context: Context) : LoginIntent() data class LoginWithGoogle(val context: Context) : LoginIntent() data object ClearError : LoginIntent() diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt index 3162eb97..249a873c 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt @@ -24,6 +24,7 @@ import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.sopt.clody.R +import com.sopt.clody.presentation.ui.auth.component.button.GoogleButton import com.sopt.clody.presentation.ui.auth.component.button.KaKaoButton import com.sopt.clody.presentation.ui.component.LoadingScreen import com.sopt.clody.presentation.ui.component.dialog.FailureDialog @@ -32,7 +33,6 @@ import com.sopt.clody.presentation.utils.base.ClodyPreview import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage import com.sopt.clody.presentation.utils.extension.repeatOnStarted import com.sopt.clody.ui.theme.ClodyTheme -import java.util.Locale @Composable fun LoginRoute( @@ -59,7 +59,7 @@ fun LoginRoute( } LoginScreen( - isLoading = state.isLoading, + state = state, onKaKaoLoginClick = { viewModel.postIntent(LoginContract.LoginIntent.LoginWithKakao(context)) }, onGoogleLoginClick = { viewModel.postIntent(LoginContract.LoginIntent.LoginWithGoogle(context)) }, ) @@ -74,13 +74,12 @@ fun LoginRoute( @Composable fun LoginScreen( - isLoading: Boolean, + state: LoginContract.LoginState, onKaKaoLoginClick: () -> Unit, onGoogleLoginClick: () -> Unit, ) { val systemUiController = rememberSystemUiController() val backgroundColor = ClodyTheme.colors.white - val currentLang = Locale.getDefault().language LaunchedEffect(Unit) { systemUiController.setStatusBarColor( @@ -91,15 +90,27 @@ fun LoginScreen( Scaffold( bottomBar = { - KaKaoButton( - text = stringResource(id = R.string.signup_btn_kakao), - onClick = onKaKaoLoginClick, - modifier = Modifier - .navigationBarsPadding() - .fillMaxWidth() - .padding(horizontal = 24.dp) - .padding(bottom = 40.dp), - ) + if (state.loginType == LoginType.KAKAO) { + KaKaoButton( + text = stringResource(id = R.string.signup_btn_kakao), + onClick = onKaKaoLoginClick, + modifier = Modifier + .navigationBarsPadding() + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(bottom = 40.dp), + ) + } else { + GoogleButton( + text = stringResource(id = R.string.signup_btn_google), + onClick = onGoogleLoginClick, + modifier = Modifier + .navigationBarsPadding() + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(bottom = 40.dp), + ) + } }, ) { innerPadding -> Column( @@ -118,7 +129,7 @@ fun LoginScreen( } } - if (isLoading) { + if (state.isLoading) { LoadingScreen() } } @@ -128,7 +139,7 @@ fun LoginScreen( fun LoginScreenPreview() { BasePreview { LoginScreen( - isLoading = false, + state = LoginContract.LoginState(), onKaKaoLoginClick = {}, onGoogleLoginClick = {}, ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginType.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginType.kt new file mode 100644 index 00000000..ff6997ab --- /dev/null +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginType.kt @@ -0,0 +1,5 @@ +package com.sopt.clody.presentation.ui.login + +enum class LoginType { + KAKAO, GOOGLE +} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt index 9f058f24..32ff7083 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt @@ -10,6 +10,7 @@ import com.sopt.clody.core.login.LoginSdk import com.sopt.clody.data.remote.dto.request.LoginRequestDto import com.sopt.clody.domain.repository.AuthRepository import com.sopt.clody.domain.repository.TokenRepository +import com.sopt.clody.presentation.utils.language.LanguageProvider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -26,6 +27,7 @@ class LoginViewModel @AssistedInject constructor( private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, private val fcmTokenProvider: FcmTokenProvider, + private val languageProvider: LanguageProvider, ) : MavericksViewModel(initialState) { private val _intents = Channel(BUFFERED) @@ -37,6 +39,7 @@ class LoginViewModel @AssistedInject constructor( .receiveAsFlow() .onEach(::handleIntent) .launchIn(viewModelScope) + postIntent(LoginContract.LoginIntent.SetLoginType) } fun postIntent(intent: LoginContract.LoginIntent) { @@ -45,6 +48,7 @@ class LoginViewModel @AssistedInject constructor( private suspend fun handleIntent(intent: LoginContract.LoginIntent) { when (intent) { + is LoginContract.LoginIntent.SetLoginType -> { setState { copy(loginType = languageProvider.getLoginType()) } } is LoginContract.LoginIntent.LoginWithKakao -> loginWithKakao(intent.context) is LoginContract.LoginIntent.LoginWithGoogle -> loginWithGoogle(intent.context) is LoginContract.LoginIntent.ClearError -> setState { copy(errorMessage = null) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt index 7110b7db..201072b5 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt @@ -15,7 +15,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -38,6 +37,7 @@ fun NicknameChangeBottomSheet( accountManagementViewModel: AccountManagementViewModel, userName: String, isValidNickname: Boolean, + nicknameMaxLength: Int, nicknameMessage: String, onDismiss: () -> Unit, ) { @@ -47,6 +47,7 @@ fun NicknameChangeBottomSheet( accountManagementViewModel = accountManagementViewModel, userName = userName, isValidNickname = isValidNickname, + nicknameMaxLength = nicknameMaxLength, nicknameMessage = nicknameMessage, onDismiss = onDismiss, ) @@ -60,14 +61,13 @@ fun NicknameChangeBottomSheetItem( accountManagementViewModel: AccountManagementViewModel, userName: String, isValidNickname: Boolean, + nicknameMaxLength: Int, nicknameMessage: String, onDismiss: () -> Unit, ) { var nickname by remember { mutableStateOf(TextFieldValue("")) } var nicknameChangeState by remember { mutableStateOf(false) } var isFocusedState by remember { mutableStateOf(false) } - val userNicknameState by accountManagementViewModel.userNicknameState.collectAsState() - val nicknameMaxLength = 10 Surface { Column( @@ -112,6 +112,7 @@ fun NicknameChangeBottomSheetItem( accountManagementViewModel.validateNickname(nickname.text) nicknameChangeState = it.text.isNotEmpty() }, + nicknameMaxLength = nicknameMaxLength, isFocused = isFocusedState, isValid = isValidNickname, onRemove = { diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeTextField.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeTextField.kt index 9e36c7a0..177041c4 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeTextField.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeTextField.kt @@ -31,6 +31,7 @@ import com.sopt.clody.ui.theme.ClodyTheme fun NickNameChangeTextField( value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit, + nicknameMaxLength: Int, isFocused: Boolean, isValid: Boolean, onRemove: () -> Unit, @@ -38,8 +39,6 @@ fun NickNameChangeTextField( modifier: Modifier = Modifier, hint: String = "", ) { - val nicknameMaxLength = 10 - Box(modifier = modifier) { BasicTextField( value = value, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt index c5be21b1..168dbfbb 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt @@ -51,6 +51,7 @@ fun AccountManagementRoute( var showRevokeDialog by remember { mutableStateOf(false) } val showFailureDialog by accountManagementViewModel.showFailureDialog.collectAsState() val failureDialogMessage by accountManagementViewModel.failureDialogMessage.collectAsState() + val nicknameMaxLength by accountManagementViewModel.nicknameMaxLength.collectAsState() LaunchedEffect(Unit) { accountManagementViewModel.fetchUserInfo() @@ -81,6 +82,7 @@ fun AccountManagementRoute( showNicknameChangeBottomSheet = showNicknameChangeBottomSheet, updateNicknameChangeBottomSheet = { state -> showNicknameChangeBottomSheet = state }, isValidNickname = isValidNickname, + nicknameMaxLength = nicknameMaxLength, nicknameMessage = nicknameMessage, showLogoutDialog = showLogoutDialog, updateLogoutDialog = { state -> showLogoutDialog = state }, @@ -100,6 +102,7 @@ fun AccountManagementScreen( showNicknameChangeBottomSheet: Boolean, updateNicknameChangeBottomSheet: (Boolean) -> Unit, isValidNickname: Boolean, + nicknameMaxLength: Int, nicknameMessage: String, showLogoutDialog: Boolean, updateLogoutDialog: (Boolean) -> Unit, @@ -160,6 +163,7 @@ fun AccountManagementScreen( NicknameChangeBottomSheet( accountManagementViewModel = accountManagementViewModel, userName = (userInfoState as UserInfoState.Success).data.name, + nicknameMaxLength = nicknameMaxLength, isValidNickname = isValidNickname, nicknameMessage = nicknameMessage, onDismiss = { updateNicknameChangeBottomSheet(false) }, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt index fc78e477..26efca67 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt @@ -7,6 +7,7 @@ import com.sopt.clody.data.datastore.TokenDataStore import com.sopt.clody.data.remote.dto.request.ModifyNicknameRequestDto import com.sopt.clody.data.remote.util.NetworkUtil import com.sopt.clody.domain.repository.AccountManagementRepository +import com.sopt.clody.presentation.utils.language.LanguageProvider import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_NETWORK_MESSAGE import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_TEMPORARY_MESSAGE import com.sopt.clody.presentation.utils.network.ErrorMessages.UNKNOWN_ERROR @@ -23,6 +24,7 @@ class AccountManagementViewModel @Inject constructor( private val tokenDataStore: TokenDataStore, private val networkUtil: NetworkUtil, @ApplicationContext private val context: Context, + private val languageProvider: LanguageProvider, ) : ViewModel() { private val _userInfoState = MutableStateFlow(UserInfoState.Idle) val userInfoState: StateFlow = _userInfoState @@ -48,6 +50,9 @@ class AccountManagementViewModel @Inject constructor( private val _failureDialogMessage = MutableStateFlow("") val failureDialogMessage: StateFlow = _failureDialogMessage + private val _nicknameMaxLength = MutableStateFlow(languageProvider.getNicknameMaxLength()) + val nicknameMaxLength: StateFlow = _nicknameMaxLength + private val maxRetryCount = 3 private var retryCount = 0 @@ -107,7 +112,7 @@ class AccountManagementViewModel @Inject constructor( fun validateNickname(nickname: String) { if (nickname.isNotEmpty()) { - val isValid = nickname.matches(Regex(NICKNAME_PATTERN)) + val isValid = nickname.matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,${_nicknameMaxLength.value}}$")) _isValidNickname.value = isValid _nicknameMessage.value = if (isValid) DEFAULT_NICKNAME_MESSAGE else FAILURE_NICKNAME_MESSAGE } else { @@ -164,7 +169,6 @@ class AccountManagementViewModel @Inject constructor( } companion object { - private const val NICKNAME_PATTERN = "^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,10}$" private const val DEFAULT_NICKNAME_MESSAGE = "특수문자, 띄어쓰기 없이 작성해주세요" private const val FAILURE_NICKNAME_MESSAGE = "사용할 수 없는 닉네임이에요" } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingOptionUrls.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingOptionUrls.kt index 68649fa2..7560af1e 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingOptionUrls.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingOptionUrls.kt @@ -2,22 +2,22 @@ package com.sopt.clody.presentation.ui.setting.screen enum class SettingOptionUrls( val enUrl: String, - val krUrl: String, + val koUrl: String, ) { NOTICES_URL( enUrl = "https://tropical-buckthorn-d17.notion.site/Notice-22ae3fedb3f480feb229e7dcc7a23887?source=copy_link", - krUrl = "https://www.notion.so/1c7e3fedb3f48029b36cf9d76c5fb6d6?pvs=21", + koUrl = "https://www.notion.so/1c7e3fedb3f48029b36cf9d76c5fb6d6?pvs=21", ), SUPPORT_FEEDBACK_URL( enUrl = "https://docs.google.com/forms/d/e/1FAIpQLSe1LJg6tYaWBY2ji3O1smCH1ux5ItbVyGVUQko-Mg609Xt9eg/viewform", - krUrl = "https://forms.gle/WnLC7VwHacufVHiv7", + koUrl = "https://forms.gle/WnLC7VwHacufVHiv7", ), TERMS_OF_SERVICE_URL( enUrl = "https://tropical-buckthorn-d17.notion.site/Clody-Terms-of-Use-22ae3fedb3f48092ace1fba817df8605?source=copy_link", - krUrl = "https://www.notion.so/1c7e3fedb3f4802c8db1f3056c03973f?pvs=21", + koUrl = "https://www.notion.so/1c7e3fedb3f4802c8db1f3056c03973f?pvs=21", ), PRIVACY_POLICY_URL( enUrl = "https://tropical-buckthorn-d17.notion.site/Clody-Privacy-Policy-22ae3fedb3f4808ab8dcc8ba60ad6cd6?source=copy_link", - krUrl = "https://www.notion.so/1c7e3fedb3f48024a334c8116255b378?pvs=21", + koUrl = "https://www.notion.so/1c7e3fedb3f48024a334c8116255b378?pvs=21", ), } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt index f349c69e..291bed09 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt @@ -20,7 +20,6 @@ import com.sopt.clody.presentation.ui.setting.component.SettingVersionInfo import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils import com.sopt.clody.ui.theme.ClodyTheme -import java.util.Locale @Composable fun SettingRoute( @@ -30,12 +29,10 @@ fun SettingRoute( navigateToWebView: (String) -> Unit, settingViewModel: SettingViewModel = hiltViewModel(), ) { - val currentLang = Locale.getDefault().language - val notice = if (currentLang == "ko") SettingOptionUrls.NOTICES_URL.krUrl else SettingOptionUrls.NOTICES_URL.enUrl - val supportFeedback = if (currentLang == "ko") SettingOptionUrls.SUPPORT_FEEDBACK_URL.krUrl else SettingOptionUrls.SUPPORT_FEEDBACK_URL.enUrl - val termsOfService = if (currentLang == "ko") SettingOptionUrls.TERMS_OF_SERVICE_URL.krUrl else SettingOptionUrls.TERMS_OF_SERVICE_URL.enUrl - val privacyPolicy = if (currentLang == "ko") SettingOptionUrls.PRIVACY_POLICY_URL.krUrl else SettingOptionUrls.PRIVACY_POLICY_URL.enUrl - + val notice by settingViewModel::noticeUrl + val supportFeedback by settingViewModel::supportFeedbackUrl + val termsOfService by settingViewModel::termsOfServiceUrl + val privacyPolicy by settingViewModel::privacyPolicyUrl val versionInfo by settingViewModel::versionInfo LaunchedEffect(Unit) { diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt index 3944f4e0..e24e922e 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.sopt.clody.R +import com.sopt.clody.presentation.utils.language.LanguageProvider import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.launch @@ -16,7 +17,13 @@ import javax.inject.Inject @HiltViewModel class SettingViewModel @Inject constructor( @ApplicationContext private val context: Context, + private val languageProvider: LanguageProvider, ) : ViewModel() { + val noticeUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.NOTICES_URL) + val supportFeedbackUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.SUPPORT_FEEDBACK_URL) + val termsOfServiceUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.TERMS_OF_SERVICE_URL) + val privacyPolicyUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.PRIVACY_POLICY_URL) + var versionInfo by mutableStateOf(null) private set diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/splash/SplashScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/splash/SplashScreen.kt index fc57d17a..c91f02d9 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/splash/SplashScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/splash/SplashScreen.kt @@ -116,8 +116,8 @@ fun SplashScreen() { @Composable fun SoftUpdateDialog( latestVersion: String, - onDismiss: () -> Unit, onConfirm: () -> Unit, + onDismiss: () -> Unit, ) { AlertDialog( onDismissRequest = onDismiss, @@ -155,7 +155,7 @@ fun HardUpdateDialog( }, confirmButton = { TextButton(onClick = onConfirm) { - Text(stringResource(R.string.dialog_soft_update_confirm)) + Text(stringResource(R.string.dialog_hard_update_confirm)) } }, dismissButton = { diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/textfield/WriteDiaryTextField.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/textfield/WriteDiaryTextField.kt index 1c495420..0f0f0a35 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/textfield/WriteDiaryTextField.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/textfield/WriteDiaryTextField.kt @@ -53,7 +53,7 @@ fun WriteDiaryTextField( ) { var isTextValid by remember { mutableStateOf( - text.replace("\\s".toRegex(), "").matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣\\W]{2,50}$")), + text.replace("\\s".toRegex(), "").matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣\\W]{2,$maxLength}$")), ) } var isFocused by remember { mutableStateOf(false) } @@ -118,7 +118,7 @@ fun WriteDiaryTextField( if (it.length <= maxLength) { onTextChange(it) val textWithoutSpaces = it.replace("\\s".toRegex(), "") - isTextValid = textWithoutSpaces.matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,50}$")) + isTextValid = textWithoutSpaces.matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,$maxLength}$")) isTextTooLong = false } else { isTextTooLong = true diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/topbar/WriteDiaryTopBar.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/topbar/WriteDiaryTopBar.kt index f84448ed..ac85584a 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/topbar/WriteDiaryTopBar.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/topbar/WriteDiaryTopBar.kt @@ -31,7 +31,7 @@ fun WriteDiaryTopBar( IconButton(onClick = onClickBack) { Icon( painter = painterResource(id = R.drawable.ic_nickname_back), - contentDescription = "뒤로가기", + contentDescription = null, tint = ClodyTheme.colors.gray01, ) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt index ffe3d65d..a3b2a9a2 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt @@ -75,6 +75,7 @@ fun WriteDiaryRoute( val entryToDelete by viewModel::entryToDelete val showDialog by viewModel::showDialog val showExitDialog by viewModel::showExitDialog + val diaryMaxLength by viewModel.diaryMaxLength.collectAsState() LaunchedEffectWhenStarted { viewModel.fetchDraftDiary(year, month, date) @@ -113,6 +114,7 @@ fun WriteDiaryRoute( showFailureDialog = showFailureDialog, failureMessage = failureMessage, showExitDialog = showExitDialog, + diaryMaxLength = diaryMaxLength, onClickBack = { AmplitudeUtils.trackEvent(AmplitudeConstraints.WRITING_DIARY_BACK) if (!viewModel.hasChangedFromInitial()) { @@ -183,6 +185,7 @@ fun WriteDiaryScreen( showEmptyFieldsMessage: Boolean, showDeleteBottomSheet: Boolean, showDialog: Boolean, + diaryMaxLength: Int, onClickBack: () -> Unit, onClickAdd: () -> Unit, onClickRemove: (Int) -> Unit, @@ -265,7 +268,7 @@ fun WriteDiaryScreen( onTextChange = { newText -> onTextChange(index, newText) }, onRemove = { onClickRemove(index) }, isRemovable = entries.size > 1, - maxLength = 50, + maxLength = diaryMaxLength, showWarning = showWarnings[index], ) } @@ -390,6 +393,7 @@ private fun WriteDiaryScreenPreview() { showEmptyFieldsMessage = false, showDeleteBottomSheet = false, showDialog = false, + diaryMaxLength = 100, onClickBack = {}, onClickAdd = {}, onClickRemove = {}, diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt index 58fcfb80..fc19175d 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt @@ -12,6 +12,7 @@ import com.sopt.clody.domain.repository.DiaryRepository import com.sopt.clody.domain.repository.DraftRepository import com.sopt.clody.domain.usecase.FetchDraftDiaryUseCase import com.sopt.clody.domain.usecase.SaveDraftDiaryUseCase +import com.sopt.clody.presentation.utils.language.LanguageProvider import com.sopt.clody.presentation.utils.network.ErrorMessages import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_NETWORK_MESSAGE import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_TEMPORARY_MESSAGE @@ -30,6 +31,7 @@ class WriteDiaryViewModel @Inject constructor( private val saveDraftDiaryUseCase: SaveDraftDiaryUseCase, private val networkUtil: NetworkUtil, private val draftRepository: DraftRepository, + private val languageProvider: LanguageProvider, ) : ViewModel() { private val _writeDiaryState = MutableStateFlow(WriteDiaryState.Idle) @@ -67,6 +69,9 @@ class WriteDiaryViewModel @Inject constructor( private var initialEntries: List = emptyList() + private val _diaryMaxLength = MutableStateFlow(languageProvider.getDiaryMaxLength()) + val diaryMaxLength: StateFlow = _diaryMaxLength + fun writeDiary(year: Int, month: Int, day: Int, contents: List) { viewModelScope.launch { if (!networkUtil.isNetworkAvailable()) { @@ -154,7 +159,7 @@ class WriteDiaryViewModel @Inject constructor( private fun isValidEntry(text: String): Boolean { val textWithoutSpaces = text.replace("\\s".toRegex(), "") - return textWithoutSpaces.matches(Regex(ENTRY_REGEX)) + return textWithoutSpaces.matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣\\W]{2,${_diaryMaxLength.value}$")) } private fun checkLimitMessage() { @@ -250,6 +255,5 @@ class WriteDiaryViewModel @Inject constructor( companion object { const val MAX_ENTRIES = 5 - const val ENTRY_REGEX = "^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣\\W]{2,50}$" } } diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt new file mode 100644 index 00000000..1ca49f26 --- /dev/null +++ b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt @@ -0,0 +1,11 @@ +package com.sopt.clody.presentation.utils.language + +import com.sopt.clody.presentation.ui.login.LoginType +import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls + +interface LanguageProvider { + fun getLoginType(): LoginType + fun getNicknameMaxLength(): Int + fun getDiaryMaxLength(): Int + fun getWebViewUrlFor(option: SettingOptionUrls): String +} diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt new file mode 100644 index 00000000..0a1976d2 --- /dev/null +++ b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt @@ -0,0 +1,41 @@ +package com.sopt.clody.presentation.utils.language + +import android.content.Context +import com.sopt.clody.presentation.ui.login.LoginType +import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls +import dagger.hilt.android.qualifiers.ApplicationContext +import java.util.Locale +import javax.inject.Inject + +class LanguageProviderImpl @Inject constructor( + @ApplicationContext private val context: Context, +) : LanguageProvider { + private val locale: Locale + get() = context.resources.configuration.locales[0] + + private fun isKorean(): Boolean = locale.language == LANGUAGE_KO + + override fun getLoginType(): LoginType { + return if (isKorean()) LoginType.KAKAO else LoginType.GOOGLE + } + + override fun getNicknameMaxLength(): Int { + return if (isKorean()) NICKNAME_MAX_LENGTH_KO else NICKNAME_MAX_LENGTH_EN + } + + override fun getDiaryMaxLength(): Int { + return if (isKorean()) DIARY_MAX_LENGTH_KO else DIARY_MAX_LENGTH_EN + } + + override fun getWebViewUrlFor(option: SettingOptionUrls): String { + return if (isKorean()) option.koUrl else option.enUrl + } + + companion object { + const val LANGUAGE_KO = "ko" + const val NICKNAME_MAX_LENGTH_EN = 15 + const val NICKNAME_MAX_LENGTH_KO = 10 + const val DIARY_MAX_LENGTH_EN = 100 + const val DIARY_MAX_LENGTH_KO = 50 + } +} diff --git a/app/src/main/java/com/sopt/clody/ui/theme/Theme.kt b/app/src/main/java/com/sopt/clody/ui/theme/Theme.kt index 6b04409c..c48dab6d 100644 --- a/app/src/main/java/com/sopt/clody/ui/theme/Theme.kt +++ b/app/src/main/java/com/sopt/clody/ui/theme/Theme.kt @@ -3,12 +3,29 @@ package com.sopt.clody.ui.theme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalConfiguration + +@Composable +fun provideTypographyByLocale(): ClodyTypography { + val configuration = LocalConfiguration.current + val locale = remember(configuration) { configuration.locales[0] } + + return if (locale.language == "ko") clodyKoreanTypography else clodyEnglishTypography +} @Composable fun ClodyTheme( content: @Composable () -> Unit, ) { - CompositionLocalProvider(content = content) + val colors = defaultClodyColors + val typography = provideTypographyByLocale() + + CompositionLocalProvider( + LocalClodyColors provides colors, + LocalClodyTypography provides typography, + content = content, + ) } object ClodyTheme { diff --git a/app/src/main/java/com/sopt/clody/ui/theme/Type.kt b/app/src/main/java/com/sopt/clody/ui/theme/Type.kt index daa8bfee..bc07b181 100644 --- a/app/src/main/java/com/sopt/clody/ui/theme/Type.kt +++ b/app/src/main/java/com/sopt/clody/ui/theme/Type.kt @@ -7,145 +7,255 @@ import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp import com.sopt.clody.R +@Immutable +data class ClodyTypography( + val head1: TextStyle, + val head2: TextStyle, + val head3: TextStyle, + val head3Medium: TextStyle, + val head4: TextStyle, + val body1SemiBold: TextStyle, + val body1Medium: TextStyle, + val body2SemiBold: TextStyle, + val body2Medium: TextStyle, + val body3SemiBold: TextStyle, + val body3Medium: TextStyle, + val body3Regular: TextStyle, + val body4SemiBold: TextStyle, + val body4Medium: TextStyle, + val detail1SemiBold: TextStyle, + val detail1Medium: TextStyle, + val detail1Regular: TextStyle, + val detail2SemiBold: TextStyle, + val detail2Medium: TextStyle, + val letterMedium: TextStyle, +) + +fun TextUnit.lineHeight(ratio: Float): TextUnit = (this.value * ratio).sp + val pretendardFontFamily = FontFamily( Font(R.font.pretendard_medium, FontWeight.Medium, FontStyle.Normal), Font(R.font.pretendard_regular, FontWeight.Normal, FontStyle.Normal), Font(R.font.pretendard_semibold, FontWeight.SemiBold, FontStyle.Normal), ) -private val pretendardTextStyle = TextStyle( +private val pretendardKoreanTextStyle = TextStyle( fontFamily = pretendardFontFamily, letterSpacing = (-0.2).sp, ) -val defaultClodyTypography = ClodyTypography( - head1 = pretendardTextStyle.copy( +private val pretendardEnglishTextStyle = TextStyle( + fontFamily = pretendardFontFamily, +) + +val clodyKoreanTypography = ClodyTypography( + head1 = pretendardKoreanTextStyle.copy( fontSize = 22.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 33.sp, + lineHeight = 22.sp.lineHeight(1.5f), ), - head2 = pretendardTextStyle.copy( + head2 = pretendardKoreanTextStyle.copy( fontSize = 20.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 30.sp, + lineHeight = 20.sp.lineHeight(1.5f), ), - head3 = pretendardTextStyle.copy( + head3 = pretendardKoreanTextStyle.copy( fontSize = 18.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 27.sp, + lineHeight = 18.sp.lineHeight(1.5f), ), - head3Medium = pretendardTextStyle.copy( + head3Medium = pretendardKoreanTextStyle.copy( fontSize = 18.sp, fontWeight = FontWeight.Medium, - lineHeight = 27.sp, + lineHeight = 18.sp.lineHeight(1.5f), ), - head4 = pretendardTextStyle.copy( + head4 = pretendardKoreanTextStyle.copy( fontSize = 17.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 25.5.sp, + lineHeight = 17.sp.lineHeight(1.5f), ), - body1SemiBold = pretendardTextStyle.copy( + body1SemiBold = pretendardKoreanTextStyle.copy( fontSize = 16.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 24.sp, + lineHeight = 16.sp.lineHeight(1.5f), ), - body1Medium = pretendardTextStyle.copy( + body1Medium = pretendardKoreanTextStyle.copy( fontSize = 16.sp, fontWeight = FontWeight.Medium, - lineHeight = 24.sp, + lineHeight = 16.sp.lineHeight(1.5f), ), - body2SemiBold = pretendardTextStyle.copy( + body2SemiBold = pretendardKoreanTextStyle.copy( fontSize = 15.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 22.5.sp, + lineHeight = 15.sp.lineHeight(1.5f), ), - body2Medium = pretendardTextStyle.copy( + body2Medium = pretendardKoreanTextStyle.copy( fontSize = 15.sp, fontWeight = FontWeight.Medium, - lineHeight = 22.5.sp, + lineHeight = 15.sp.lineHeight(1.5f), ), - body3SemiBold = pretendardTextStyle.copy( + body3SemiBold = pretendardKoreanTextStyle.copy( fontSize = 14.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 21.sp, + lineHeight = 14.sp.lineHeight(1.5f), ), - body3Medium = pretendardTextStyle.copy( + body3Medium = pretendardKoreanTextStyle.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, - lineHeight = 21.sp, + lineHeight = 14.sp.lineHeight(1.5f), ), - body3Regular = pretendardTextStyle.copy( + body3Regular = pretendardKoreanTextStyle.copy( fontSize = 14.sp, fontWeight = FontWeight.Normal, - lineHeight = 21.sp, + lineHeight = 14.sp.lineHeight(1.5f), ), - body4Medium = pretendardTextStyle.copy( + body4SemiBold = pretendardKoreanTextStyle.copy( fontSize = 13.sp, - fontWeight = FontWeight.Medium, - lineHeight = 19.5.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 13.sp.lineHeight(1.5f), ), - body4SemiBold = pretendardTextStyle.copy( + body4Medium = pretendardKoreanTextStyle.copy( fontSize = 13.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 19.5.sp, + fontWeight = FontWeight.Medium, + lineHeight = 13.sp.lineHeight(1.5f), ), - detail1SemiBold = pretendardTextStyle.copy( + detail1SemiBold = pretendardKoreanTextStyle.copy( fontSize = 12.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 18.sp, + lineHeight = 12.sp.lineHeight(1.5f), ), - detail1Medium = pretendardTextStyle.copy( + detail1Medium = pretendardKoreanTextStyle.copy( fontSize = 12.sp, fontWeight = FontWeight.Medium, - lineHeight = 18.sp, + lineHeight = 12.sp.lineHeight(1.5f), ), - detail1Regular = pretendardTextStyle.copy( + detail1Regular = pretendardKoreanTextStyle.copy( fontSize = 12.sp, fontWeight = FontWeight.Normal, - lineHeight = 18.sp, + lineHeight = 12.sp.lineHeight(1.5f), ), - detail2SemiBold = pretendardTextStyle.copy( + detail2SemiBold = pretendardKoreanTextStyle.copy( fontSize = 10.sp, fontWeight = FontWeight.SemiBold, - lineHeight = 15.sp, + lineHeight = 10.sp.lineHeight(1.5f), ), - detail2Medium = pretendardTextStyle.copy( + detail2Medium = pretendardKoreanTextStyle.copy( fontSize = 10.sp, fontWeight = FontWeight.Medium, - lineHeight = 15.sp, + lineHeight = 10.sp.lineHeight(1.5f), ), - letterMedium = pretendardTextStyle.copy( + letterMedium = pretendardKoreanTextStyle.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, - lineHeight = 26.6.sp, + lineHeight = 14.sp.lineHeight(1.9f), ), ) -@Immutable -data class ClodyTypography( - val head1: TextStyle, - val head2: TextStyle, - val head3: TextStyle, - val head3Medium: TextStyle, - val head4: TextStyle, - val body1SemiBold: TextStyle, - val body1Medium: TextStyle, - val body2SemiBold: TextStyle, - val body2Medium: TextStyle, - val body3SemiBold: TextStyle, - val body3Medium: TextStyle, - val body3Regular: TextStyle, - val body4SemiBold: TextStyle, - val body4Medium: TextStyle, - val detail1SemiBold: TextStyle, - val detail1Medium: TextStyle, - val detail1Regular: TextStyle, - val detail2SemiBold: TextStyle, - val detail2Medium: TextStyle, - val letterMedium: TextStyle, +val clodyEnglishTypography = ClodyTypography( + head1 = pretendardEnglishTextStyle.copy( + fontSize = 22.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 22.sp.lineHeight(1.4f), + ), + head2 = pretendardEnglishTextStyle.copy( + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 20.sp.lineHeight(1.4f), + ), + head3 = pretendardEnglishTextStyle.copy( + fontSize = 18.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 18.sp.lineHeight(1.4f), + ), + head3Medium = pretendardEnglishTextStyle.copy( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + lineHeight = 18.sp.lineHeight(1.4f), + ), + head4 = pretendardEnglishTextStyle.copy( + fontSize = 17.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 17.sp.lineHeight(1.4f), + ), + body1SemiBold = pretendardEnglishTextStyle.copy( + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp.lineHeight(1.3f), + ), + body1Medium = pretendardEnglishTextStyle.copy( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + lineHeight = 16.sp.lineHeight(1.3f), + ), + body2SemiBold = pretendardEnglishTextStyle.copy( + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 15.sp.lineHeight(1.3f), + ), + body2Medium = pretendardEnglishTextStyle.copy( + fontSize = 15.sp, + fontWeight = FontWeight.Medium, + lineHeight = 15.sp.lineHeight(1.3f), + ), + body3SemiBold = pretendardEnglishTextStyle.copy( + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 14.sp.lineHeight(1.3f), + ), + body3Medium = pretendardEnglishTextStyle.copy( + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + lineHeight = 14.sp.lineHeight(1.3f), + ), + body3Regular = pretendardEnglishTextStyle.copy( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + lineHeight = 14.sp.lineHeight(1.3f), + ), + body4SemiBold = pretendardEnglishTextStyle.copy( + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 13.sp.lineHeight(1.3f), + ), + body4Medium = pretendardEnglishTextStyle.copy( + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + lineHeight = 13.sp.lineHeight(1.3f), + ), + detail1SemiBold = pretendardEnglishTextStyle.copy( + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 12.sp.lineHeight(1.3f), + ), + detail1Medium = pretendardEnglishTextStyle.copy( + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + lineHeight = 12.sp.lineHeight(1.3f), + ), + detail1Regular = pretendardEnglishTextStyle.copy( + fontSize = 12.sp, + fontWeight = FontWeight.Normal, + lineHeight = 12.sp.lineHeight(1.3f), + ), + detail2SemiBold = pretendardEnglishTextStyle.copy( + fontSize = 10.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 10.sp.lineHeight(1.3f), + ), + detail2Medium = pretendardEnglishTextStyle.copy( + fontSize = 10.sp, + fontWeight = FontWeight.Medium, + lineHeight = 10.sp.lineHeight(1.3f), + ), + letterMedium = pretendardEnglishTextStyle.copy( + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + lineHeight = 14.sp.lineHeight(1.8f), + ), ) -val LocalClodyTypography = staticCompositionLocalOf { defaultClodyTypography } +val LocalClodyTypography = staticCompositionLocalOf { clodyEnglishTypography } diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index a7ea8898..46e4a0c4 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -118,6 +118,7 @@ 필수 업데이트 버전 %1$s으로 업데이트가 필요합니다. + 업데이트 앱 종료 임시저장된 일기를 이어 쓸까요? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3cfef1ae..5e61e957 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,6 +118,7 @@ Update Required Please update to version %1$s to continue + Update Exit App Pick up where you left off?