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
- 기념일 설정에 실패했습니다.
-