diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/toast/ToastHost.kt b/core/design-system/src/main/java/com/twix/designsystem/components/toast/ToastHost.kt index 8c430c9f..41fd40bd 100644 --- a/core/design-system/src/main/java/com/twix/designsystem/components/toast/ToastHost.kt +++ b/core/design-system/src/main/java/com/twix/designsystem/components/toast/ToastHost.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -87,7 +88,10 @@ fun ToastHost( } Box( - modifier = modifier.fillMaxSize(), + modifier = + modifier + .fillMaxSize() + .imePadding(), contentAlignment = Alignment.BottomCenter, ) { AnimatedVisibility( diff --git a/core/design-system/src/main/res/drawable/ic_arrow_left.xml b/core/design-system/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 00000000..ff4ad4f7 --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,13 @@ + + + diff --git a/feature/onboarding/src/main/res/drawable/ic_copy.xml b/core/design-system/src/main/res/drawable/ic_copy.xml similarity index 100% rename from feature/onboarding/src/main/res/drawable/ic_copy.xml rename to core/design-system/src/main/res/drawable/ic_copy.xml diff --git a/feature/onboarding/src/main/res/drawable/ic_direct.xml b/core/design-system/src/main/res/drawable/ic_direct.xml similarity index 100% rename from feature/onboarding/src/main/res/drawable/ic_direct.xml rename to core/design-system/src/main/res/drawable/ic_direct.xml diff --git a/core/design-system/src/main/res/drawable/ic_invite.xml b/core/design-system/src/main/res/drawable/ic_invite.xml new file mode 100644 index 00000000..16c0b9df --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_invite.xml @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 67fdf85c..dec32ae1 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -114,6 +114,9 @@ 인증샷 촬영을 위해서 카메라 권한이 필요해요. 알림 목록 조회에 실패했습니다. 찌르기에 실패했습니다. + 이미 사용된 초대 코드입니다. + 초대 코드가 복사되었어요 + 잘못된 초대 코드입니다 %s\n목표를 이루셨나요? @@ -160,4 +163,34 @@ 온보딩 정보를 불러오는 데 실패했습니다. 다시 시도해주세요 함께니까 멈추지 않아요.\n지금 바로 키피럽 시작하기! + + 닉네임을 입력해 주세요. + 닉네임 2~8자 + 짝꿍에게 보일\n내 이름을 입력해 주세요 + 닉네임을 입력해 주세요. + 닉네임 2~8자 + 완료 + 2자에서 8자 이내로 닉네임을 입력해주세요. + 프로필 설정 요청에 실패했습니다. + + 짝꿍과 연결하고\n함께 키피럽 시작하세요 + 초대장 보내기 + 짝꿍에게 코드를 받았다면? + 직접 연결하기 + 해지한 커플 복구하려면? + 아래 내용을 포함하여 문의해 주시기 바랍니다.\n고객센터 메일 - ttwixteamm@gmail.com + 본인 로그인 계정 메일 + 짝꿍의 로그인 계정 메일 + 해지 일시 + 내 조회코드 조회에 실패했습니다. + 커플 연결 요청에 실패했어요 + + 짝꿍에게 받은\n초대 코드를 써주세요 + 내 초대 코드 + 받은 코드 쓰기 + + 우리의 기념일을 등록해 주세요 + YYYY-MM-DD + 기념일 설정에 실패했습니다. + diff --git a/feature/login/src/main/java/com/twix/login/LoginScreen.kt b/feature/login/src/main/java/com/twix/login/LoginScreen.kt index bbca89cc..91d7e219 100644 --- a/feature/login/src/main/java/com/twix/login/LoginScreen.kt +++ b/feature/login/src/main/java/com/twix/login/LoginScreen.kt @@ -3,33 +3,23 @@ package com.twix.login import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -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.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.boundsInParent -import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.twix.designsystem.R -import com.twix.designsystem.components.button.LoginButton import com.twix.designsystem.components.text.AppText import com.twix.designsystem.components.toast.ToastManager import com.twix.designsystem.components.toast.model.ToastData @@ -39,6 +29,7 @@ import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.OnboardingStatus import com.twix.domain.model.enums.AppTextStyle import com.twix.domain.model.enums.LoginType +import com.twix.login.component.LoginButton import com.twix.login.contract.LoginIntent import com.twix.login.contract.LoginSideEffect import com.twix.ui.base.ObserveAsEvents @@ -82,10 +73,6 @@ fun LoginRoute( @Composable private fun LoginScreen(onClickLogin: (LoginType) -> Unit) { - var imageBottomPx by remember { mutableFloatStateOf(0f) } - val density = LocalDensity.current - val offsetPx = with(density) { 34.dp.toPx() } - Column( modifier = Modifier @@ -111,41 +98,23 @@ private fun LoginScreen(onClickLogin: (LoginType) -> Unit) { Spacer(Modifier.height(27.dp)) - Box(modifier = Modifier.fillMaxSize()) { - Image( - imageVector = ImageVector.vectorResource(R.drawable.ic_singing), - contentDescription = null, - modifier = - Modifier - .onGloballyPositioned { coordinates -> - imageBottomPx = coordinates.boundsInParent().bottom - }, - ) + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_singing), + contentDescription = null, + ) - if (imageBottomPx != 0f) { - Column( - modifier = - Modifier - .padding(horizontal = 20.dp) - .offset { - IntOffset( - x = 0, - /** - * singing 이미지 하단 기준으로 로그인 버튼을 배치하고 - * 이미지와 버튼이 겹치는 만큼(34dp) 상단으로 이동 - * */ - y = (imageBottomPx - offsetPx).toInt(), - ) - }, - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - LoginType.entries.forEach { type -> - LoginButton( - type = type, - onClickLogin = onClickLogin, - ) - } - } + Column( + modifier = + Modifier + .padding(horizontal = 20.dp) + .padding(top = 29.dp, bottom = 27.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + LoginType.entries.forEach { type -> + LoginButton( + type = type, + onClickLogin = onClickLogin, + ) } } } diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/button/LoginButton.kt b/feature/login/src/main/java/com/twix/login/component/LoginButton.kt similarity index 97% rename from core/design-system/src/main/java/com/twix/designsystem/components/button/LoginButton.kt rename to feature/login/src/main/java/com/twix/login/component/LoginButton.kt index bcb058ed..e86b976b 100644 --- a/core/design-system/src/main/java/com/twix/designsystem/components/button/LoginButton.kt +++ b/feature/login/src/main/java/com/twix/login/component/LoginButton.kt @@ -1,4 +1,4 @@ -package com.twix.designsystem.components.button +package com.twix.login.component import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -27,7 +27,7 @@ import com.twix.domain.model.enums.LoginType import com.twix.ui.extension.noRippleClickable @Composable -fun LoginButton( +internal fun LoginButton( type: LoginType, onClickLogin: (LoginType) -> Unit, modifier: Modifier = Modifier, diff --git a/feature/login/src/main/java/com/twix/login/navigation/LoginNavGraph.kt b/feature/login/src/main/java/com/twix/login/navigation/LoginNavGraph.kt index 95901f41..31929a76 100644 --- a/feature/login/src/main/java/com/twix/login/navigation/LoginNavGraph.kt +++ b/feature/login/src/main/java/com/twix/login/navigation/LoginNavGraph.kt @@ -38,11 +38,7 @@ object LoginNavGraph : NavGraphContributor { OnboardingStatus.COMPLETED -> return@LoginRoute } - navController.navigate(destination) { - popUpTo(NavRoutes.LoginGraph.route) { - inclusive = true - } - } + navController.navigate(destination) }, ) } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt b/feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt index 51f9543a..14e4f3ea 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt @@ -1,12 +1,15 @@ package com.twix.onboarding import androidx.lifecycle.viewModelScope +import com.twix.designsystem.R +import com.twix.designsystem.components.toast.model.ToastType import com.twix.domain.model.OnboardingStatus +import com.twix.domain.model.invitecode.InviteCode import com.twix.domain.repository.NotificationRepository import com.twix.domain.repository.OnBoardingRepository -import com.twix.onboarding.model.OnBoardingIntent -import com.twix.onboarding.model.OnBoardingSideEffect -import com.twix.onboarding.model.OnBoardingUiState +import com.twix.onboarding.contract.OnBoardingIntent +import com.twix.onboarding.contract.OnBoardingSideEffect +import com.twix.onboarding.contract.OnBoardingUiState import com.twix.result.AppError import com.twix.ui.base.BaseViewModel import kotlinx.coroutines.launch @@ -16,11 +19,26 @@ class OnBoardingViewModel( private val onBoardingRepository: OnBoardingRepository, private val notificationRepository: NotificationRepository, ) : BaseViewModel(OnBoardingUiState()) { - fun fetchMyInviteCode() { + init { + fetchMyInviteCode() + } + + private fun fetchMyInviteCode() { launchResult( block = { onBoardingRepository.fetchInviteCode() }, - onSuccess = { reduce { updateMyInviteCode(it.value) } }, - onError = { emitSideEffect(OnBoardingSideEffect.CoupleConnection.ShowFetchMyInviteCodeFailToast) }, + onSuccess = { fetchedInviteCode -> + reduce { + copy( + inviteCode = + inviteCode.copy( + myInviteCode = fetchedInviteCode.value, + ), + ) + } + }, + onError = { + showToast(R.string.onboarding_couple_fetch_my_invite_code_fail, ToastType.ERROR) + }, ) } @@ -30,8 +48,7 @@ class OnBoardingViewModel( is OnBoardingIntent.WriteInviteCode -> reduceInviteCode(intent.value) OnBoardingIntent.ConnectCouple -> connectCouple() OnBoardingIntent.CopyInviteCode -> - emitSideEffect(OnBoardingSideEffect.InviteCode.ShowCopyInviteCodeSuccessToast) - + emitSideEffect(OnBoardingSideEffect.InviteCode.CopyInviteCode(currentState.inviteCode.myInviteCode)) // 프로필 설정 화면 is OnBoardingIntent.WriteNickName -> reduceNickName(intent.value) OnBoardingIntent.SubmitNickName -> handleSubmitNickname() @@ -41,12 +58,26 @@ class OnBoardingViewModel( OnBoardingIntent.SubmitDday -> anniversarySetup() is OnBoardingIntent.SubmitMarketingConsent -> - initNotificationSettings(intent.isPushEnabled, intent.isMarketingEnabled, intent.isNightMarketingEnabled) + initNotificationSettings( + intent.isPushEnabled, + intent.isMarketingEnabled, + intent.isNightMarketingEnabled, + ) } } private fun reduceInviteCode(value: String) { - reduce { updatePartnerInviteCode(value) } + val isValidInviteCode = InviteCode.create(value).isSuccess + + reduce { + copy( + inviteCode = + inviteCode.copy( + partnerInviteCode = value, + isValid = isValidInviteCode, + ), + ) + } } private fun connectCouple() { @@ -70,20 +101,20 @@ class OnBoardingViewModel( * 초대 코드를 잘못 입력한 경우 * */ if (error.message == INVALID_INVITE_CODE_MESSAGE) { - emitSideEffect(OnBoardingSideEffect.InviteCode.ShowInvalidInviteCodeToast) + showToast(R.string.toast_invalid_invite_code, ToastType.ERROR) } else if (error.message == ALREADY_USED_INVITE_CODE_MESSAGE) { /** * 상대방이 이미 연결한 경우 * */ emitSideEffect(OnBoardingSideEffect.InviteCode.NavigateToNext) } else { - emitSideEffect(OnBoardingSideEffect.InviteCode.ShowConnectCoupleConnectFailToast) + showToast(R.string.onboarding_couple_connection_fail, ToastType.ERROR) } } } private fun reduceNickName(value: String) { - reduce { updateNickName(value) } + reduce { copy(profile = profile.updateNickname(value)) } } private fun handleSubmitNickname() { @@ -91,7 +122,7 @@ class OnBoardingViewModel( profileSetup() } else { viewModelScope.launch { - emitSideEffect(OnBoardingSideEffect.ProfileSetting.ShowInvalidNickNameToast) + showToast(R.string.onboarding_profile_invalid_name_length_toast, ToastType.ERROR) } } } @@ -100,7 +131,7 @@ class OnBoardingViewModel( launchResult( block = { onBoardingRepository.profileSetup(currentState.profile.nickname) }, onSuccess = { fetchOnboardingStatus() }, - onError = { emitSideEffect(OnBoardingSideEffect.ProfileSetting.ShowProfileSetupFailToast) }, + onError = { showToast(R.string.onboarding_profile_setup_fail, ToastType.ERROR) }, ) } @@ -112,7 +143,7 @@ class OnBoardingViewModel( val sideEffect = when (onboardingStatus) { OnboardingStatus.ANNIVERSARY_SETUP -> - OnBoardingSideEffect.ProfileSetting.NavigateToDDaySetting + OnBoardingSideEffect.ProfileSetting.NavigateToNext OnboardingStatus.COMPLETED -> OnBoardingSideEffect.ProfileSetting.NavigateToHome @@ -122,14 +153,15 @@ class OnBoardingViewModel( emitSideEffect(sideEffect) } }, - onError = { - // 에러처리 추가 - }, ) } private fun reduceDday(value: LocalDate) { - reduce { updateDday(value) } + reduce { + copy( + dDay = dDay.updateAnniversaryDate(value), + ) + } } private fun anniversarySetup() { @@ -139,7 +171,7 @@ class OnBoardingViewModel( viewModelScope.launch { emitSideEffect(OnBoardingSideEffect.DdaySetting.NavigateToHome) } }, onError = { - emitSideEffect(OnBoardingSideEffect.DdaySetting.ShowAnniversarySetupFailToast) + showToast(R.string.onboarding_dday_setup_fail, ToastType.ERROR) }, ) } @@ -161,6 +193,13 @@ class OnBoardingViewModel( ) } + private suspend fun showToast( + message: Int, + type: ToastType, + ) { + emitSideEffect(OnBoardingSideEffect.ShowToast(message, type)) + } + companion object { private const val ALREADY_USED_INVITE_CODE_MESSAGE = "이미 사용된 초대 코드입니다." private const val INVALID_INVITE_CODE_MESSAGE = "유효하지 않은 초대 코드입니다." diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt b/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingIntent.kt similarity index 95% rename from feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt rename to feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingIntent.kt index e7ae7655..e13f74af 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingIntent.kt @@ -1,4 +1,4 @@ -package com.twix.onboarding.model +package com.twix.onboarding.contract import com.twix.ui.base.Intent import java.time.LocalDate diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingSideEffect.kt b/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingSideEffect.kt new file mode 100644 index 00000000..61a086c4 --- /dev/null +++ b/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingSideEffect.kt @@ -0,0 +1,29 @@ +package com.twix.onboarding.contract + +import com.twix.designsystem.components.toast.model.ToastType +import com.twix.ui.base.SideEffect + +sealed interface OnBoardingSideEffect : SideEffect { + sealed interface ProfileSetting : OnBoardingSideEffect { + data object NavigateToNext : ProfileSetting + + data object NavigateToHome : ProfileSetting + } + + sealed interface InviteCode : OnBoardingSideEffect { + data object NavigateToNext : InviteCode + + data class CopyInviteCode( + val inviteCode: String, + ) : InviteCode + } + + sealed interface DdaySetting : OnBoardingSideEffect { + data object NavigateToHome : DdaySetting + } + + data class ShowToast( + val message: Int, + val type: ToastType, + ) : OnBoardingSideEffect +} diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingUiState.kt b/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingUiState.kt similarity index 52% rename from feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingUiState.kt rename to feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingUiState.kt index 8316982d..c5e675d5 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingUiState.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/contract/OnBoardingUiState.kt @@ -1,11 +1,10 @@ -package com.twix.onboarding.model +package com.twix.onboarding.contract import androidx.compose.runtime.Immutable import com.twix.onboarding.dday.DdayUiModel import com.twix.onboarding.invite.InviteCodeUiModel import com.twix.onboarding.profile.ProfileUiModel import com.twix.ui.base.State -import java.time.LocalDate @Immutable data class OnBoardingUiState( @@ -15,12 +14,4 @@ data class OnBoardingUiState( ) : State { val isValidNickName: Boolean get() = profile.isValid - - fun updateNickName(value: String) = copy(profile = profile.updateNickname(value)) - - fun updateMyInviteCode(value: String) = copy(inviteCode = inviteCode.updateMyInviteCode(value)) - - fun updatePartnerInviteCode(value: String) = copy(inviteCode = inviteCode.updatePartnerInviteCode(value)) - - fun updateDday(value: LocalDate) = copy(dDay = dDay.updateAnniversaryDate(value)) } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt b/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt index 51a31c09..6d89879e 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt @@ -6,15 +6,18 @@ import android.content.pm.PackageManager import android.os.Build import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row 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.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberUpdatedState @@ -24,30 +27,31 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat +import com.twix.designsystem.R import com.twix.designsystem.components.bottomsheet.CommonBottomSheet import com.twix.designsystem.components.bottomsheet.model.CommonBottomSheetConfig import com.twix.designsystem.components.dialog.MarketingDialog import com.twix.designsystem.components.text.AppText import com.twix.designsystem.components.toast.ToastManager import com.twix.designsystem.components.toast.model.ToastData -import com.twix.designsystem.components.toast.model.ToastType import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import com.twix.onboarding.OnBoardingViewModel -import com.twix.onboarding.R +import com.twix.onboarding.contract.OnBoardingIntent +import com.twix.onboarding.contract.OnBoardingSideEffect import com.twix.onboarding.couple.component.ConnectButton +import com.twix.onboarding.couple.component.CoupleConnectTopbar +import com.twix.onboarding.couple.component.InvitationButton import com.twix.onboarding.couple.component.RestoreCoupleBottomSheetContent -import com.twix.onboarding.model.OnBoardingIntent -import com.twix.onboarding.model.OnBoardingSideEffect import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import org.koin.compose.koinInject @@ -57,25 +61,20 @@ fun CoupleConnectRoute( viewModel: OnBoardingViewModel, toastManager: ToastManager = koinInject(), navigateToNext: () -> Unit, + navigateToBack: () -> Unit, ) { var showMarketingDialog by rememberSaveable { mutableStateOf(true) } var showRestoreSheet by rememberSaveable { mutableStateOf(false) } val context = LocalContext.current val currentContext by rememberUpdatedState(context) - LaunchedEffect(Unit) { - viewModel.fetchMyInviteCode() - } - - val fetchMyInviteCodeFailMessage = - stringResource(R.string.onboarding_couple_fetch_my_invite_code_fail) ObserveAsEvents(viewModel.sideEffect) { sideEffect -> when (sideEffect) { - OnBoardingSideEffect.CoupleConnection.ShowFetchMyInviteCodeFailToast -> { + is OnBoardingSideEffect.ShowToast -> { toastManager.tryShow( ToastData( - message = fetchMyInviteCodeFailMessage, - type = ToastType.ERROR, + message = currentContext.getString(sideEffect.message), + type = sideEffect.type, ), ) } @@ -91,6 +90,7 @@ fun CoupleConnectRoute( onClickConnect = navigateToNext, onClickRestore = { showRestoreSheet = true }, onDismissSheet = { showRestoreSheet = false }, + onClickBack = navigateToBack, ) MarketingDialog( @@ -117,15 +117,22 @@ fun CoupleConnectScreen( onClickConnect: () -> Unit, onClickRestore: () -> Unit, onDismissSheet: () -> Unit, + onClickBack: () -> Unit, ) { + val scrollState = rememberScrollState() + Box( modifier = Modifier .fillMaxSize() .background(color = CommonColor.White), ) { - Column { - Spacer(modifier = Modifier.height(80.24.dp)) + Column( + Modifier.verticalScroll(scrollState), + ) { + CoupleConnectTopbar(onClickBack = onClickBack) + + Spacer(modifier = Modifier.height(8.dp)) AppText( text = stringResource(R.string.onboarding_couple_connect_description), @@ -134,16 +141,17 @@ fun CoupleConnectScreen( modifier = Modifier.padding(start = 24.dp), ) - Spacer(modifier = Modifier.height(11.76.dp)) + Spacer(modifier = Modifier.height(4.dp)) Image( - imageVector = ImageVector.vectorResource(R.drawable.img_couple_connect), + imageVector = ImageVector.vectorResource(R.drawable.ic_invite), contentDescription = null, modifier = Modifier.align(Alignment.CenterHorizontally), ) - Spacer(modifier = Modifier.height(47.dp)) - // InvitationButton(onClick = onClickSend) + Spacer(modifier = Modifier.height(2.dp)) + + InvitationButton(onClick = onClickSend) Spacer(modifier = Modifier.height(20.dp)) @@ -151,16 +159,25 @@ fun CoupleConnectScreen( Spacer(modifier = Modifier.height(20.dp)) - AppText( - text = stringResource(R.string.onboarding_couple_restore), - style = AppTextStyle.B1, - color = GrayColor.C400, - textAlign = TextAlign.Center, + Row( modifier = Modifier .fillMaxWidth() .noRippleClickable(onClick = onClickRestore), - ) + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + AppText( + text = stringResource(R.string.onboarding_couple_restore), + style = AppTextStyle.B1, + color = GrayColor.C400, + ) + + Image( + painter = painterResource(R.drawable.ic_arrow_m_right), + contentDescription = null, + ) + } } CommonBottomSheet( @@ -196,6 +213,7 @@ private fun CoupleConnectScreenPreview() { onClickConnect = {}, onClickRestore = {}, onDismissSheet = {}, + onClickBack = {}, ) } } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/ConnectButton.kt b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/ConnectButton.kt index 0c7b1a2d..e619f454 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/ConnectButton.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/ConnectButton.kt @@ -3,7 +3,7 @@ package com.twix.onboarding.couple.component import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -12,24 +12,20 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.FontWeight.Companion.ExtraBold import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import com.twix.designsystem.R import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor -import com.twix.designsystem.theme.NanumSquareNeoFamily import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle -import com.twix.onboarding.R import com.twix.ui.extension.noRippleClickable @Composable @@ -48,35 +44,34 @@ internal fun ConnectButton( color = GrayColor.C500, width = 1.2.dp, shape = RoundedCornerShape(12.dp), - ).noRippleClickable { onClickConnect() }, + ).noRippleClickable(onClick = onClickConnect), verticalAlignment = Alignment.CenterVertically, ) { Column( - modifier = Modifier.padding(start = 25.dp), - verticalArrangement = Arrangement.Center, + modifier = + Modifier + .padding(vertical = 20.dp) + .padding(start = 25.dp), ) { - AppText( - text = stringResource(R.string.onboarding_couple_connect_direct_description), - style = AppTextStyle.C1, - color = GrayColor.C400, - ) - - Spacer(modifier = Modifier.height(3.dp)) - - Row( - verticalAlignment = Alignment.CenterVertically, + Box( + modifier = Modifier.height(18.dp), + contentAlignment = Alignment.Center, ) { - Text( - text = stringResource(R.string.onboarding_couple_connect_direct), - fontFamily = NanumSquareNeoFamily, - fontWeight = ExtraBold, - color = GrayColor.C500, - fontSize = 16.sp, - lineHeight = 22.2.sp, + AppText( + text = stringResource(R.string.onboarding_couple_connect_direct_description), + style = AppTextStyle.C1, + color = GrayColor.C400, ) + } + + Spacer(Modifier.weight(1f)) + Box( + modifier = Modifier.height(24.dp), + contentAlignment = Alignment.Center, + ) { AppText( - text = stringResource(R.string.onboarding_couple_connect), + text = stringResource(R.string.onboarding_couple_direct_connect_button_title), style = AppTextStyle.T2, color = GrayColor.C500, ) diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/CoupleConnectTopbar.kt b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/CoupleConnectTopbar.kt new file mode 100644 index 00000000..4213b597 --- /dev/null +++ b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/CoupleConnectTopbar.kt @@ -0,0 +1,34 @@ +package com.twix.onboarding.couple.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.twix.designsystem.R +import com.twix.ui.extension.noRippleClickable + +@Composable +internal fun CoupleConnectTopbar(onClickBack: () -> Unit) { + Box( + Modifier + .fillMaxWidth() + .height(72.dp), + contentAlignment = Alignment.CenterStart, + ) { + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_left), + contentDescription = null, + modifier = + Modifier + .noRippleClickable(onClick = onClickBack) + .padding(start = 10.dp), + ) + } +} diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/InvitationButton.kt b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/InvitationButton.kt index a0d4a8ff..aced9136 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/InvitationButton.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/InvitationButton.kt @@ -12,12 +12,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.twix.designsystem.R import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle -import com.twix.onboarding.R import com.twix.ui.extension.noRippleClickable @Composable diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/RestoreCoupleBottomSheetContent.kt b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/RestoreCoupleBottomSheetContent.kt index d2d4de61..d4fa7ba2 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/RestoreCoupleBottomSheetContent.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/couple/component/RestoreCoupleBottomSheetContent.kt @@ -16,18 +16,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.twix.designsystem.R import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle -import com.twix.onboarding.R - -private val RestoreBgColor = Color(0xFFF6F7F7) -private val BulletColor = Color(0xFF999999) @Composable internal fun RestoreCoupleBottomSheetContent() { @@ -35,8 +31,8 @@ internal fun RestoreCoupleBottomSheetContent() { modifier = Modifier .fillMaxWidth() - .padding(30.dp), - verticalArrangement = Arrangement.spacedBy(18.dp), + .padding(bottom = 16.dp) + .padding(horizontal = 30.dp), ) { AppText( text = stringResource(R.string.onboarding_couple_restore), @@ -44,18 +40,22 @@ internal fun RestoreCoupleBottomSheetContent() { color = GrayColor.C500, ) + Spacer(Modifier.height(3.dp)) + AppText( text = stringResource(R.string.onboarding_couple_restore_bottom_sheet_content), style = AppTextStyle.B2, color = GrayColor.C400, ) + Spacer(Modifier.height(18.dp)) + Column( modifier = Modifier .fillMaxWidth() .height(78.dp) - .background(RestoreBgColor, RoundedCornerShape(12.dp)), + .background(GrayColor.C050, RoundedCornerShape(12.dp)), verticalArrangement = Arrangement.Center, ) { BulletItem(stringResource(R.string.onboarding_couple_restore_content_my_email)) @@ -69,13 +69,15 @@ internal fun RestoreCoupleBottomSheetContent() { private fun BulletItem(text: String) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(start = 16.dp), + modifier = + Modifier + .padding(start = 16.dp), ) { Box( modifier = Modifier .size(6.dp) - .background(BulletColor, CircleShape), + .background(GrayColor.C300, CircleShape), ) Spacer(Modifier.width(8.dp)) @@ -83,7 +85,7 @@ private fun BulletItem(text: String) { AppText( text = text, style = AppTextStyle.B4, - color = BulletColor, + color = GrayColor.C300, ) } } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt b/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt index 02cb7871..0eb8ab97 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt @@ -12,12 +12,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue 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.lifecycle.compose.collectAsStateWithLifecycle +import com.twix.designsystem.R import com.twix.designsystem.components.bottomsheet.CommonBottomSheet import com.twix.designsystem.components.bottomsheet.model.CommonBottomSheetConfig import com.twix.designsystem.components.button.AppButton @@ -25,17 +28,15 @@ import com.twix.designsystem.components.calendar.Calendar import com.twix.designsystem.components.text.AppText import com.twix.designsystem.components.toast.ToastManager import com.twix.designsystem.components.toast.model.ToastData -import com.twix.designsystem.components.toast.model.ToastType import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import com.twix.onboarding.OnBoardingViewModel -import com.twix.onboarding.R +import com.twix.onboarding.contract.OnBoardingIntent +import com.twix.onboarding.contract.OnBoardingSideEffect import com.twix.onboarding.dday.component.DDayField import com.twix.onboarding.dday.component.DdayTopBar -import com.twix.onboarding.model.OnBoardingIntent -import com.twix.onboarding.model.OnBoardingSideEffect import com.twix.ui.base.ObserveAsEvents import org.koin.compose.koinInject import java.time.LocalDate @@ -48,17 +49,18 @@ fun DdayRoute( toastManager: ToastManager = koinInject(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current + val currentContext by rememberUpdatedState(context) var showCalendarBottomSheet by remember { mutableStateOf(false) } - val ddaySetUpFailMessage = stringResource(R.string.onboarding_dday_setup_fail) ObserveAsEvents(viewModel.sideEffect) { sideEffect -> when (sideEffect) { OnBoardingSideEffect.DdaySetting.NavigateToHome -> navigateToHome() - OnBoardingSideEffect.DdaySetting.ShowAnniversarySetupFailToast -> { + is OnBoardingSideEffect.ShowToast -> { toastManager.tryShow( ToastData( - message = ddaySetUpFailMessage, - type = ToastType.ERROR, + message = currentContext.getString(sideEffect.message), + type = sideEffect.type, ), ) } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/dday/component/DDayField.kt b/feature/onboarding/src/main/java/com/twix/onboarding/dday/component/DDayField.kt index 131d6164..69180cb4 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/dday/component/DDayField.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/dday/component/DDayField.kt @@ -16,15 +16,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.twix.designsystem.R import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle -import com.twix.onboarding.R import com.twix.onboarding.dday.DdayUiModel import com.twix.ui.extension.noRippleClickable import java.time.LocalDate -import com.twix.designsystem.R as DesR @Composable internal fun DDayField( @@ -59,7 +58,7 @@ internal fun DDayField( ) Image( - imageVector = ImageVector.vectorResource(DesR.drawable.ic_calendar), + imageVector = ImageVector.vectorResource(R.drawable.ic_calendar), contentDescription = null, modifier = Modifier diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt b/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt index fd000099..b7e1bd60 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt @@ -1,6 +1,7 @@ package com.twix.onboarding.invite import android.content.ClipData +import android.os.Build import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -20,24 +21,30 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalClipboard +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.toClipEntry import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.twix.designsystem.R import com.twix.designsystem.components.button.AppButton import com.twix.designsystem.components.text.AppText import com.twix.designsystem.components.toast.ToastManager @@ -48,10 +55,9 @@ import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import com.twix.onboarding.OnBoardingViewModel -import com.twix.onboarding.R +import com.twix.onboarding.contract.OnBoardingIntent +import com.twix.onboarding.contract.OnBoardingSideEffect import com.twix.onboarding.invite.component.InviteCodeTextField -import com.twix.onboarding.model.OnBoardingIntent -import com.twix.onboarding.model.OnBoardingSideEffect import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import com.twix.ui.keyboard.Keyboard @@ -69,39 +75,46 @@ internal fun InviteCodeRoute( val uiState by viewModel.uiState.collectAsStateWithLifecycle() val coroutineScope = rememberCoroutineScope() val keyboardState by keyboardAsState() + val context = LocalContext.current + val currentContext by rememberUpdatedState(context) val clipboard = LocalClipboard.current - val inviteCodeSuccessMessage = stringResource(R.string.onboarding_invite_code_copy) - val invalidInviteCodeMessage = stringResource(R.string.onboarding_invite_invalid_invite_code_fail) - val coupleConnectionFailMessage = stringResource(R.string.onboarding_couple_connection_fail) - ObserveAsEvents(viewModel.sideEffect) { sideEffect -> when (sideEffect) { - OnBoardingSideEffect.InviteCode.ShowCopyInviteCodeSuccessToast -> { - toastManager.tryShow( - ToastData( - message = inviteCodeSuccessMessage, - type = ToastType.SUCCESS, - ), - ) - } - OnBoardingSideEffect.InviteCode.ShowInvalidInviteCodeToast -> { - toastManager.tryShow( - ToastData( - message = invalidInviteCodeMessage, - type = ToastType.ERROR, - ), - ) - } - OnBoardingSideEffect.InviteCode.ShowConnectCoupleConnectFailToast -> { + is OnBoardingSideEffect.ShowToast -> { toastManager.tryShow( ToastData( - message = coupleConnectionFailMessage, - type = ToastType.ERROR, + message = currentContext.getString(sideEffect.message), + type = sideEffect.type, ), ) } + OnBoardingSideEffect.InviteCode.NavigateToNext -> navigateToNext() + is OnBoardingSideEffect.InviteCode.CopyInviteCode -> { + coroutineScope.launch { + val clipData = + ClipData + .newPlainText( + "inviteCode", + sideEffect.inviteCode, + ).toClipEntry() + clipboard.setClipEntry(clipData) + } + + /** + * https://developer.android.com/develop/ui/views/touch-and-input/copy-paste?hl=ko#duplicate-notifications + * */ + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + toastManager.tryShow( + ToastData( + currentContext.getString(R.string.toast_invite_code_copy), + ToastType.SUCCESS, + ), + ) + } + } + else -> Unit } } @@ -112,17 +125,7 @@ internal fun InviteCodeRoute( navigateToBack = navigateToBack, onChangeInviteCode = { viewModel.dispatch(OnBoardingIntent.WriteInviteCode(it)) }, onComplete = { viewModel.dispatch(OnBoardingIntent.ConnectCouple) }, - onCopyInviteCode = { - val clipData = - ClipData.newPlainText( - "inviteCode", - uiState.inviteCode.myInviteCode, - ) - coroutineScope.launch { - clipboard.setClipEntry(clipData.toClipEntry()) - } - viewModel.dispatch(OnBoardingIntent.CopyInviteCode) - }, + onCopyInviteCode = { viewModel.dispatch(OnBoardingIntent.CopyInviteCode) }, ) } @@ -135,11 +138,7 @@ private fun InviteCodeScreen( onComplete: () -> Unit, onCopyInviteCode: () -> Unit, ) { - val focusRequester = remember { FocusRequester() } - - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } + val scrollState = rememberScrollState() Box( modifier = @@ -147,25 +146,12 @@ private fun InviteCodeScreen( .fillMaxSize() .background(CommonColor.White), ) { - Box( + Column( modifier = Modifier - .fillMaxWidth() - .height(72.dp) - .padding(horizontal = 10.dp, vertical = 14.dp), - contentAlignment = Alignment.CenterStart, + .fillMaxSize() + .verticalScroll(scrollState), ) { - Image( - imageVector = ImageVector.vectorResource(com.twix.designsystem.R.drawable.ic_arrow_m_left), - contentDescription = null, - modifier = - Modifier - .size(44.dp) - .noRippleClickable(onClick = navigateToBack), - ) - } - - Column { Spacer(modifier = Modifier.height(8.dp)) AnimatedVisibility( @@ -197,15 +183,28 @@ private fun InviteCodeScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - AppText( - text = stringResource(R.string.onboarding_invite_code_my_invite_code), - style = AppTextStyle.B3, - color = GrayColor.C400, - ) + Box( + modifier = + Modifier + .height(18.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + AppText( + text = stringResource(R.string.onboarding_invite_code_my_invite_code), + style = AppTextStyle.B3, + color = GrayColor.C400, + ) + } Spacer(modifier = Modifier.height(6.dp)) Row( + modifier = + Modifier + .height(39.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { AppText( @@ -226,25 +225,52 @@ private fun InviteCodeScreen( Spacer(modifier = Modifier.height(52.dp)) - AppText( - text = stringResource(R.string.onboarding_invite_code_write_invite_code), - style = AppTextStyle.B3, - color = GrayColor.C500, - modifier = Modifier.align(Alignment.CenterHorizontally), - ) + Box( + modifier = + Modifier + .fillMaxWidth() + .height(18.dp), + contentAlignment = Alignment.Center, + ) { + AppText( + text = stringResource(R.string.onboarding_invite_code_write_invite_code), + style = AppTextStyle.B3, + color = GrayColor.C500, + ) + } Spacer(modifier = Modifier.height(12.dp)) InviteCodeTextField( inviteCode = uiModel.partnerInviteCode, onValueChange = onChangeInviteCode, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + + Box( + modifier = + Modifier + .fillMaxWidth() + .height(72.dp) + .padding(horizontal = 10.dp, vertical = 14.dp), + contentAlignment = Alignment.CenterStart, + ) { + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_m_left), + contentDescription = null, modifier = Modifier - .focusRequester(focusRequester) - .align(Alignment.CenterHorizontally), + .size(44.dp) + .noRippleClickable(onClick = navigateToBack), ) } + TopGradientOverlay( + visible = scrollState.value > 0, + modifier = Modifier.align(Alignment.TopCenter), + ) + AppButton( text = stringResource(R.string.onboarding_profile_button_title), onClick = { onComplete() }, @@ -262,16 +288,54 @@ private fun InviteCodeScreen( } } -@Preview(name = "InviteCodeScreen", showBackground = true) +@Composable +private fun TopGradientOverlay( + visible: Boolean, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + visible = visible, + enter = fadeIn(), + exit = fadeOut(), + modifier = modifier, + ) { + Box( + modifier = + Modifier + .fillMaxWidth() + .height(96.dp) + .background( + brush = + Brush.verticalGradient( + colors = + listOf( + CommonColor.White, + CommonColor.White.copy(alpha = 0.6f), + Color.Transparent, + ), + ), + ), + ) + } +} + +@Preview(showBackground = true) @Composable private fun InviteCodeScreenPreview() { TwixTheme { + var textState by remember { mutableStateOf("") } + InviteCodeScreen( - uiModel = InviteCodeUiModel(), - onChangeInviteCode = {}, + uiModel = + InviteCodeUiModel( + partnerInviteCode = textState, + myInviteCode = "ABCDEFG", + isValid = textState.length == 6, + ), + onChangeInviteCode = { textState = it }, onComplete = {}, navigateToBack = {}, - keyboardState = Keyboard.Opened, + keyboardState = Keyboard.Closed, onCopyInviteCode = {}, ) } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeUiModel.kt b/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeUiModel.kt index 82f37d58..f24f98dc 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeUiModel.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeUiModel.kt @@ -1,21 +1,10 @@ package com.twix.onboarding.invite import androidx.compose.runtime.Immutable -import com.twix.domain.model.invitecode.InviteCode @Immutable data class InviteCodeUiModel( val myInviteCode: String = "", val partnerInviteCode: String = "", val isValid: Boolean = false, -) { - fun updateMyInviteCode(value: String): InviteCodeUiModel = copy(myInviteCode = value) - - fun updatePartnerInviteCode(value: String): InviteCodeUiModel = - InviteCode - .create(value) - .fold( - onSuccess = { copy(partnerInviteCode = value, isValid = true) }, - onFailure = { copy(partnerInviteCode = value, isValid = false) }, - ) -} +) diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/invite/component/InviteCodeTextField.kt b/feature/onboarding/src/main/java/com/twix/onboarding/invite/component/InviteCodeTextField.kt index 5ddd979d..82cab94e 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/invite/component/InviteCodeTextField.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/invite/component/InviteCodeTextField.kt @@ -1,10 +1,5 @@ package com.twix.onboarding.invite.component -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -14,12 +9,12 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.twix.designsystem.components.text.AppText @@ -36,21 +31,24 @@ fun InviteCodeTextField( ) { BasicTextField( modifier = modifier, - value = TextFieldValue(inviteCode, selection = TextRange(inviteCode.length)), - onValueChange = { - if (it.text.length <= InviteCode.INVITE_CODE_LENGTH) { - onValueChange(it.text) + value = inviteCode, + onValueChange = { newText -> + val filtered = newText.filterNot { it.isWhitespace() } + if (filtered.length <= InviteCode.INVITE_CODE_LENGTH && filtered != inviteCode) { + onValueChange(filtered) } }, decorationBox = { - Row( - horizontalArrangement = Arrangement.spacedBy(4.29.dp), - ) { + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { repeat(InviteCode.INVITE_CODE_LENGTH) { index -> CodeBox(index, inviteCode) } } }, + keyboardOptions = + KeyboardOptions( + capitalization = KeyboardCapitalization.Characters, + ), ) } @@ -60,12 +58,6 @@ private fun CodeBox( code: String, ) { val isFocused = code.length == index - val infiniteTransition = rememberInfiniteTransition() - val alpha by infiniteTransition.animateFloat( - 0f, - 1f, - infiniteRepeatable(tween(500), RepeatMode.Reverse), - ) Box( modifier = @@ -91,19 +83,22 @@ private fun CodeBox( ) } - isFocused -> { - Box( - modifier = - Modifier - .width(2.dp) - .height(24.dp) - .background(GrayColor.C500.copy(alpha = alpha)), - ) - } + isFocused -> Cursor() } } } +@Composable +private fun Cursor() { + Box( + modifier = + Modifier + .width(2.dp) + .height(24.dp) + .background(GrayColor.C500), + ) +} + @Preview(showBackground = true) @Composable fun InviteCodeTextFieldPreview() { diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingSideEffect.kt b/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingSideEffect.kt deleted file mode 100644 index 9bff5899..00000000 --- a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingSideEffect.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.twix.onboarding.model - -import com.twix.ui.base.SideEffect - -sealed interface OnBoardingSideEffect : SideEffect { - sealed interface ProfileSetting : OnBoardingSideEffect { - data object ShowInvalidNickNameToast : ProfileSetting - - data object ShowProfileSetupFailToast : ProfileSetting - - data object NavigateToDDaySetting : ProfileSetting - - data object NavigateToHome : ProfileSetting - } - - sealed interface CoupleConnection : OnBoardingSideEffect { - data object ShowFetchMyInviteCodeFailToast : CoupleConnection - } - - sealed interface InviteCode : OnBoardingSideEffect { - data object ShowCopyInviteCodeSuccessToast : InviteCode - - data object ShowInvalidInviteCodeToast : InviteCode - - data object ShowConnectCoupleConnectFailToast : InviteCode - - data object NavigateToNext : InviteCode - } - - sealed interface DdaySetting : OnBoardingSideEffect { - data object NavigateToHome : DdaySetting - - data object ShowAnniversarySetupFailToast : DdaySetting - } -} diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt b/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt index 1408fb1f..446d6fba 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt @@ -34,6 +34,7 @@ object OnboardingNavGraph : NavGraphContributor { navigateToNext = { navController.navigate(NavRoutes.InviteRoute.route) }, + navigateToBack = navController::popBackStack, viewModel = vm, ) } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt b/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt index ff6c037f..e257aeca 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt @@ -16,35 +16,35 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.twix.designsystem.R import com.twix.designsystem.components.button.AppButton import com.twix.designsystem.components.text.AppText import com.twix.designsystem.components.text_field.UnderlineTextField import com.twix.designsystem.components.toast.ToastManager import com.twix.designsystem.components.toast.model.ToastData -import com.twix.designsystem.components.toast.model.ToastType import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.SystemColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import com.twix.onboarding.OnBoardingViewModel -import com.twix.onboarding.R -import com.twix.onboarding.model.OnBoardingIntent -import com.twix.onboarding.model.OnBoardingSideEffect +import com.twix.onboarding.contract.OnBoardingIntent +import com.twix.onboarding.contract.OnBoardingSideEffect import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import org.koin.compose.koinInject -import com.twix.designsystem.R as DesR @Composable fun ProfileRoute( @@ -54,34 +54,22 @@ fun ProfileRoute( toastManager: ToastManager = koinInject(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - val notValidNickNameMessage = - stringResource(R.string.onboarding_profile_invalid_name_length_toast) - - val profileSetupFailMessage = stringResource(R.string.onboarding_profile_setup_fail) + val context = LocalContext.current + val currentContext by rememberUpdatedState(context) ObserveAsEvents(viewModel.sideEffect) { sideEffect -> when (sideEffect) { - OnBoardingSideEffect.ProfileSetting.ShowInvalidNickNameToast -> { - toastManager.tryShow( - ToastData( - message = notValidNickNameMessage, - type = ToastType.ERROR, - ), - ) - } - - OnBoardingSideEffect.ProfileSetting.ShowProfileSetupFailToast -> { + is OnBoardingSideEffect.ShowToast -> { toastManager.tryShow( ToastData( - message = profileSetupFailMessage, - type = ToastType.ERROR, + message = currentContext.getString(sideEffect.message), + type = sideEffect.type, ), ) } OnBoardingSideEffect.ProfileSetting.NavigateToHome -> navigateToHome() - OnBoardingSideEffect.ProfileSetting.NavigateToDDaySetting -> navigateToDday() + OnBoardingSideEffect.ProfileSetting.NavigateToNext -> navigateToDday() else -> Unit } } @@ -132,7 +120,7 @@ private fun ProfileScreen( onValueChange = onChangeNickName, trailing = { Image( - imageVector = ImageVector.vectorResource(DesR.drawable.ic_clear_text), + imageVector = ImageVector.vectorResource(R.drawable.ic_clear_text), contentDescription = null, modifier = Modifier.noRippleClickable { onChangeNickName("") }, ) @@ -150,7 +138,7 @@ private fun ProfileScreen( verticalAlignment = Alignment.CenterVertically, ) { Icon( - imageVector = ImageVector.vectorResource(DesR.drawable.ic_check_success), + imageVector = ImageVector.vectorResource(R.drawable.ic_check_success), contentDescription = null, tint = if (uiModel.isValid) SystemColor.Success else GrayColor.C300, ) diff --git a/feature/onboarding/src/main/res/drawable/img_couple_connect.xml b/feature/onboarding/src/main/res/drawable/img_couple_connect.xml deleted file mode 100644 index b9b80ff7..00000000 --- a/feature/onboarding/src/main/res/drawable/img_couple_connect.xml +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/feature/onboarding/src/main/res/values/strings.xml b/feature/onboarding/src/main/res/values/strings.xml deleted file mode 100644 index ef6d2daf..00000000 --- a/feature/onboarding/src/main/res/values/strings.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - 닉네임을 입력해 주세요. - 닉네임 2~8자 - 짝꿍에게 보일\n내 이름을 입력해 주세요 - 닉네임을 입력해 주세요. - 닉네임 2~8자 - 완료 - 2자에서 8자 이내로 닉네임을 입력해주세요. - 프로필 설정 요청에 실패했습니다. - - - 짝꿍과 연결하고\n함께 키피럽 시작하세요 - 초대장 보내기 - 짝꿍에게 코드를 받았다면? - 직접 - 연결하기 - 해지한 커플 복구하려면? - 아래 내용을 포함하여 문의해 주시기 바랍니다.\n고객센터 메일 - ttwixteamm@gmail.com - 본인 로그인 계정 메일 - 짝꿍의 로그인 계정 메일 - 해지 일시 - 내 조회코드 조회에 실패했습니다. - 커플 연결 요청에 실패했어요 - - - 짝꿍에게 받은\n초대 코드를 써주세요 - 내 초대 코드 - 받은 코드 쓰기 - 초대 코드가 복사되었어요 - 잘못된 초대 코드입니다 - - - 우리의 기념일을 등록해 주세요 - YYYY-MM-DD - 기념일 설정에 실패했습니다. -