Skip to content

Commit 7d8c984

Browse files
authored
Merge pull request #128 from YAPP-Github/refactor/#123-onboarding-qa
온보딩 화면 QA 반영
2 parents ede7f36 + 6417ad2 commit 7d8c984

28 files changed

Lines changed: 920 additions & 565 deletions

File tree

core/design-system/src/main/java/com/twix/designsystem/components/toast/ToastHost.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Row
1515
import androidx.compose.foundation.layout.Spacer
1616
import androidx.compose.foundation.layout.fillMaxSize
1717
import androidx.compose.foundation.layout.fillMaxWidth
18+
import androidx.compose.foundation.layout.imePadding
1819
import androidx.compose.foundation.layout.padding
1920
import androidx.compose.foundation.layout.size
2021
import androidx.compose.foundation.layout.width
@@ -87,7 +88,10 @@ fun ToastHost(
8788
}
8889

8990
Box(
90-
modifier = modifier.fillMaxSize(),
91+
modifier =
92+
modifier
93+
.fillMaxSize()
94+
.imePadding(),
9195
contentAlignment = Alignment.BottomCenter,
9296
) {
9397
AnimatedVisibility(
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="44dp"
3+
android:height="44dp"
4+
android:viewportWidth="44"
5+
android:viewportHeight="44">
6+
<path
7+
android:pathData="M20.504,31H27.5C28.605,31 29.5,29.849 29.5,28.429V15.57C29.5,14.151 28.605,13 27.5,13H20.5M18,25.5L14.5,22L18,18.5M24.5,21.996H14.5"
8+
android:strokeLineJoin="round"
9+
android:strokeWidth="1.5"
10+
android:fillColor="#00000000"
11+
android:strokeColor="#171717"
12+
android:strokeLineCap="round"/>
13+
</vector>
File renamed without changes.

feature/onboarding/src/main/res/drawable/ic_direct.xml renamed to core/design-system/src/main/res/drawable/ic_direct.xml

File renamed without changes.

core/design-system/src/main/res/drawable/ic_invite.xml

Lines changed: 450 additions & 0 deletions
Large diffs are not rendered by default.

core/design-system/src/main/res/values/strings.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@
117117
<string name="toast_camera_permission_request">인증샷 촬영을 위해서 카메라 권한이 필요해요.</string>
118118
<string name="toast_fetch_notification_failed">알림 목록 조회에 실패했습니다.</string>
119119
<string name="toast_poke_goal_failed">찌르기에 실패했습니다.</string>
120+
<string name="toast_already_used_invite_code_message">이미 사용된 초대 코드입니다.</string>
121+
<string name="toast_invite_code_copy">초대 코드가 복사되었어요</string>
122+
<string name="toast_invalid_invite_code">잘못된 초대 코드입니다</string>
120123
<string name="toast_reaction_fail">리액션 요청에 실패했어요.</string>
121124
<string name="toast_photolog_upload_fail">인증샷 등록에 실패했습니다.</string>
122125
<string name="toast_image_translate_fail">이미지 변환에 실패했습니다.</string>
@@ -162,4 +165,34 @@
162165
<string name="fetch_onboarding_status_fail_message">온보딩 정보를 불러오는 데 실패했습니다. 다시 시도해주세요</string>
163166
<string name="login_title_message">함께니까 멈추지 않아요.\n지금 바로 키피럽 시작하기!</string>
164167

168+
<!-- 온보딩 화면 -->
169+
<string name="onboarding_name_placeholder">닉네임을 입력해 주세요.</string>
170+
<string name="onboarding_name_helper">닉네임 2~8자</string>
171+
<string name="onboarding_profile_title">짝꿍에게 보일\n내 이름을 입력해 주세요</string>
172+
<string name="onboarding_profile_placeholder">닉네임을 입력해 주세요.</string>
173+
<string name="onboarding_profile_helper">닉네임 2~8자</string>
174+
<string name="onboarding_profile_button_title">완료</string>
175+
<string name="onboarding_profile_invalid_name_length_toast">2자에서 8자 이내로 닉네임을 입력해주세요.</string>
176+
<string name="onboarding_profile_setup_fail">프로필 설정 요청에 실패했습니다.</string>
177+
178+
<string name="onboarding_couple_connect_description">짝꿍과 연결하고\n함께 키피럽 시작하세요</string>
179+
<string name="onboarding_couple_connect_send_invitation">초대장 보내기</string>
180+
<string name="onboarding_couple_connect_direct_description">짝꿍에게 코드를 받았다면?</string>
181+
<string name="onboarding_couple_direct_connect_button_title">직접 연결하기</string>
182+
<string name="onboarding_couple_restore">해지한 커플 복구하려면?</string>
183+
<string name="onboarding_couple_restore_bottom_sheet_content">아래 내용을 포함하여 문의해 주시기 바랍니다.\n고객센터 메일 - ttwixteamm@gmail.com</string>
184+
<string name="onboarding_couple_restore_content_my_email">본인 로그인 계정 메일</string>
185+
<string name="onboarding_couple_restore_content_partner_email">짝꿍의 로그인 계정 메일</string>
186+
<string name="onboarding_couple_restore_content_restore_date">해지 일시</string>
187+
<string name="onboarding_couple_fetch_my_invite_code_fail">내 조회코드 조회에 실패했습니다.</string>
188+
<string name="onboarding_couple_connection_fail">커플 연결 요청에 실패했어요</string>
189+
190+
<string name="onboarding_invite_code_plz_write_invite_code">짝꿍에게 받은\n초대 코드를 써주세요</string>
191+
<string name="onboarding_invite_code_my_invite_code">내 초대 코드</string>
192+
<string name="onboarding_invite_code_write_invite_code">받은 코드 쓰기</string>
193+
194+
<string name="onboarding_dday_plz_set_dday">우리의 기념일을 등록해 주세요</string>
195+
<string name="onboarding_dday_placeholder">YYYY-MM-DD</string>
196+
<string name="onboarding_dday_setup_fail">기념일 설정에 실패했습니다.</string>
197+
165198
</resources>

feature/login/src/main/java/com/twix/login/LoginScreen.kt

Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,23 @@ package com.twix.login
33
import androidx.compose.foundation.Image
44
import androidx.compose.foundation.background
55
import androidx.compose.foundation.layout.Arrangement
6-
import androidx.compose.foundation.layout.Box
76
import androidx.compose.foundation.layout.Column
87
import androidx.compose.foundation.layout.Spacer
98
import androidx.compose.foundation.layout.fillMaxSize
109
import androidx.compose.foundation.layout.height
11-
import androidx.compose.foundation.layout.offset
1210
import androidx.compose.foundation.layout.padding
1311
import androidx.compose.runtime.Composable
1412
import androidx.compose.runtime.getValue
15-
import androidx.compose.runtime.mutableFloatStateOf
16-
import androidx.compose.runtime.remember
1713
import androidx.compose.runtime.rememberCoroutineScope
1814
import androidx.compose.runtime.rememberUpdatedState
19-
import androidx.compose.runtime.setValue
2015
import androidx.compose.ui.Modifier
2116
import androidx.compose.ui.graphics.vector.ImageVector
22-
import androidx.compose.ui.layout.boundsInParent
23-
import androidx.compose.ui.layout.onGloballyPositioned
2417
import androidx.compose.ui.platform.LocalContext
25-
import androidx.compose.ui.platform.LocalDensity
2618
import androidx.compose.ui.res.stringResource
2719
import androidx.compose.ui.res.vectorResource
2820
import androidx.compose.ui.tooling.preview.Preview
29-
import androidx.compose.ui.unit.IntOffset
3021
import androidx.compose.ui.unit.dp
3122
import com.twix.designsystem.R
32-
import com.twix.designsystem.components.button.LoginButton
3323
import com.twix.designsystem.components.text.AppText
3424
import com.twix.designsystem.components.toast.ToastManager
3525
import com.twix.designsystem.components.toast.model.ToastData
@@ -39,6 +29,7 @@ import com.twix.designsystem.theme.TwixTheme
3929
import com.twix.domain.model.OnboardingStatus
4030
import com.twix.domain.model.enums.AppTextStyle
4131
import com.twix.domain.model.enums.LoginType
32+
import com.twix.login.component.LoginButton
4233
import com.twix.login.contract.LoginIntent
4334
import com.twix.login.contract.LoginSideEffect
4435
import com.twix.ui.base.ObserveAsEvents
@@ -82,10 +73,6 @@ fun LoginRoute(
8273

8374
@Composable
8475
private fun LoginScreen(onClickLogin: (LoginType) -> Unit) {
85-
var imageBottomPx by remember { mutableFloatStateOf(0f) }
86-
val density = LocalDensity.current
87-
val offsetPx = with(density) { 34.dp.toPx() }
88-
8976
Column(
9077
modifier =
9178
Modifier
@@ -111,41 +98,23 @@ private fun LoginScreen(onClickLogin: (LoginType) -> Unit) {
11198

11299
Spacer(Modifier.height(27.dp))
113100

114-
Box(modifier = Modifier.fillMaxSize()) {
115-
Image(
116-
imageVector = ImageVector.vectorResource(R.drawable.ic_singing),
117-
contentDescription = null,
118-
modifier =
119-
Modifier
120-
.onGloballyPositioned { coordinates ->
121-
imageBottomPx = coordinates.boundsInParent().bottom
122-
},
123-
)
101+
Image(
102+
imageVector = ImageVector.vectorResource(R.drawable.ic_singing),
103+
contentDescription = null,
104+
)
124105

125-
if (imageBottomPx != 0f) {
126-
Column(
127-
modifier =
128-
Modifier
129-
.padding(horizontal = 20.dp)
130-
.offset {
131-
IntOffset(
132-
x = 0,
133-
/**
134-
* singing 이미지 하단 기준으로 로그인 버튼을 배치하고
135-
* 이미지와 버튼이 겹치는 만큼(34dp) 상단으로 이동
136-
* */
137-
y = (imageBottomPx - offsetPx).toInt(),
138-
)
139-
},
140-
verticalArrangement = Arrangement.spacedBy(12.dp),
141-
) {
142-
LoginType.entries.forEach { type ->
143-
LoginButton(
144-
type = type,
145-
onClickLogin = onClickLogin,
146-
)
147-
}
148-
}
106+
Column(
107+
modifier =
108+
Modifier
109+
.padding(horizontal = 20.dp)
110+
.padding(top = 29.dp, bottom = 27.dp),
111+
verticalArrangement = Arrangement.spacedBy(12.dp),
112+
) {
113+
LoginType.entries.forEach { type ->
114+
LoginButton(
115+
type = type,
116+
onClickLogin = onClickLogin,
117+
)
149118
}
150119
}
151120
}

core/design-system/src/main/java/com/twix/designsystem/components/button/LoginButton.kt renamed to feature/login/src/main/java/com/twix/login/component/LoginButton.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.twix.designsystem.components.button
1+
package com.twix.login.component
22

33
import androidx.compose.foundation.Image
44
import androidx.compose.foundation.background
@@ -27,7 +27,7 @@ import com.twix.domain.model.enums.LoginType
2727
import com.twix.ui.extension.noRippleClickable
2828

2929
@Composable
30-
fun LoginButton(
30+
internal fun LoginButton(
3131
type: LoginType,
3232
onClickLogin: (LoginType) -> Unit,
3333
modifier: Modifier = Modifier,

feature/login/src/main/java/com/twix/login/navigation/LoginNavGraph.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,7 @@ object LoginNavGraph : NavGraphContributor {
3838
OnboardingStatus.COMPLETED -> return@LoginRoute
3939
}
4040

41-
navController.navigate(destination) {
42-
popUpTo(NavRoutes.LoginGraph.route) {
43-
inclusive = true
44-
}
45-
}
41+
navController.navigate(destination)
4642
},
4743
)
4844
}

feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package com.twix.onboarding
22

33
import androidx.lifecycle.viewModelScope
4+
import com.twix.designsystem.R
5+
import com.twix.designsystem.components.toast.model.ToastType
46
import com.twix.domain.model.OnboardingStatus
7+
import com.twix.domain.model.invitecode.InviteCode
58
import com.twix.domain.repository.NotificationRepository
69
import com.twix.domain.repository.OnBoardingRepository
7-
import com.twix.onboarding.model.OnBoardingIntent
8-
import com.twix.onboarding.model.OnBoardingSideEffect
9-
import com.twix.onboarding.model.OnBoardingUiState
10+
import com.twix.onboarding.contract.OnBoardingIntent
11+
import com.twix.onboarding.contract.OnBoardingSideEffect
12+
import com.twix.onboarding.contract.OnBoardingUiState
1013
import com.twix.result.AppError
1114
import com.twix.ui.base.BaseViewModel
1215
import kotlinx.coroutines.launch
@@ -16,11 +19,26 @@ class OnBoardingViewModel(
1619
private val onBoardingRepository: OnBoardingRepository,
1720
private val notificationRepository: NotificationRepository,
1821
) : BaseViewModel<OnBoardingUiState, OnBoardingIntent, OnBoardingSideEffect>(OnBoardingUiState()) {
19-
fun fetchMyInviteCode() {
22+
init {
23+
fetchMyInviteCode()
24+
}
25+
26+
private fun fetchMyInviteCode() {
2027
launchResult(
2128
block = { onBoardingRepository.fetchInviteCode() },
22-
onSuccess = { reduce { updateMyInviteCode(it.value) } },
23-
onError = { emitSideEffect(OnBoardingSideEffect.CoupleConnection.ShowFetchMyInviteCodeFailToast) },
29+
onSuccess = { fetchedInviteCode ->
30+
reduce {
31+
copy(
32+
inviteCode =
33+
inviteCode.copy(
34+
myInviteCode = fetchedInviteCode.value,
35+
),
36+
)
37+
}
38+
},
39+
onError = {
40+
showToast(R.string.onboarding_couple_fetch_my_invite_code_fail, ToastType.ERROR)
41+
},
2442
)
2543
}
2644

@@ -30,8 +48,7 @@ class OnBoardingViewModel(
3048
is OnBoardingIntent.WriteInviteCode -> reduceInviteCode(intent.value)
3149
OnBoardingIntent.ConnectCouple -> connectCouple()
3250
OnBoardingIntent.CopyInviteCode ->
33-
emitSideEffect(OnBoardingSideEffect.InviteCode.ShowCopyInviteCodeSuccessToast)
34-
51+
emitSideEffect(OnBoardingSideEffect.InviteCode.CopyInviteCode(currentState.inviteCode.myInviteCode))
3552
// 프로필 설정 화면
3653
is OnBoardingIntent.WriteNickName -> reduceNickName(intent.value)
3754
OnBoardingIntent.SubmitNickName -> handleSubmitNickname()
@@ -41,12 +58,26 @@ class OnBoardingViewModel(
4158
OnBoardingIntent.SubmitDday -> anniversarySetup()
4259

4360
is OnBoardingIntent.SubmitMarketingConsent ->
44-
initNotificationSettings(intent.isPushEnabled, intent.isMarketingEnabled, intent.isNightMarketingEnabled)
61+
initNotificationSettings(
62+
intent.isPushEnabled,
63+
intent.isMarketingEnabled,
64+
intent.isNightMarketingEnabled,
65+
)
4566
}
4667
}
4768

4869
private fun reduceInviteCode(value: String) {
49-
reduce { updatePartnerInviteCode(value) }
70+
val isValidInviteCode = InviteCode.create(value).isSuccess
71+
72+
reduce {
73+
copy(
74+
inviteCode =
75+
inviteCode.copy(
76+
partnerInviteCode = value,
77+
isValid = isValidInviteCode,
78+
),
79+
)
80+
}
5081
}
5182

5283
private fun connectCouple() {
@@ -70,28 +101,28 @@ class OnBoardingViewModel(
70101
* 초대 코드를 잘못 입력한 경우
71102
* */
72103
if (error.message == INVALID_INVITE_CODE_MESSAGE) {
73-
emitSideEffect(OnBoardingSideEffect.InviteCode.ShowInvalidInviteCodeToast)
104+
showToast(R.string.toast_invalid_invite_code, ToastType.ERROR)
74105
} else if (error.message == ALREADY_USED_INVITE_CODE_MESSAGE) {
75106
/**
76107
* 상대방이 이미 연결한 경우
77108
* */
78109
emitSideEffect(OnBoardingSideEffect.InviteCode.NavigateToNext)
79110
} else {
80-
emitSideEffect(OnBoardingSideEffect.InviteCode.ShowConnectCoupleConnectFailToast)
111+
showToast(R.string.onboarding_couple_connection_fail, ToastType.ERROR)
81112
}
82113
}
83114
}
84115

85116
private fun reduceNickName(value: String) {
86-
reduce { updateNickName(value) }
117+
reduce { copy(profile = profile.updateNickname(value)) }
87118
}
88119

89120
private fun handleSubmitNickname() {
90121
if (currentState.isValidNickName) {
91122
profileSetup()
92123
} else {
93124
viewModelScope.launch {
94-
emitSideEffect(OnBoardingSideEffect.ProfileSetting.ShowInvalidNickNameToast)
125+
showToast(R.string.onboarding_profile_invalid_name_length_toast, ToastType.ERROR)
95126
}
96127
}
97128
}
@@ -100,7 +131,7 @@ class OnBoardingViewModel(
100131
launchResult(
101132
block = { onBoardingRepository.profileSetup(currentState.profile.nickname) },
102133
onSuccess = { fetchOnboardingStatus() },
103-
onError = { emitSideEffect(OnBoardingSideEffect.ProfileSetting.ShowProfileSetupFailToast) },
134+
onError = { showToast(R.string.onboarding_profile_setup_fail, ToastType.ERROR) },
104135
)
105136
}
106137

@@ -112,7 +143,7 @@ class OnBoardingViewModel(
112143
val sideEffect =
113144
when (onboardingStatus) {
114145
OnboardingStatus.ANNIVERSARY_SETUP ->
115-
OnBoardingSideEffect.ProfileSetting.NavigateToDDaySetting
146+
OnBoardingSideEffect.ProfileSetting.NavigateToNext
116147

117148
OnboardingStatus.COMPLETED ->
118149
OnBoardingSideEffect.ProfileSetting.NavigateToHome
@@ -122,14 +153,15 @@ class OnBoardingViewModel(
122153
emitSideEffect(sideEffect)
123154
}
124155
},
125-
onError = {
126-
// 에러처리 추가
127-
},
128156
)
129157
}
130158

131159
private fun reduceDday(value: LocalDate) {
132-
reduce { updateDday(value) }
160+
reduce {
161+
copy(
162+
dDay = dDay.updateAnniversaryDate(value),
163+
)
164+
}
133165
}
134166

135167
private fun anniversarySetup() {
@@ -139,7 +171,7 @@ class OnBoardingViewModel(
139171
viewModelScope.launch { emitSideEffect(OnBoardingSideEffect.DdaySetting.NavigateToHome) }
140172
},
141173
onError = {
142-
emitSideEffect(OnBoardingSideEffect.DdaySetting.ShowAnniversarySetupFailToast)
174+
showToast(R.string.onboarding_dday_setup_fail, ToastType.ERROR)
143175
},
144176
)
145177
}
@@ -161,6 +193,13 @@ class OnBoardingViewModel(
161193
)
162194
}
163195

196+
private suspend fun showToast(
197+
message: Int,
198+
type: ToastType,
199+
) {
200+
emitSideEffect(OnBoardingSideEffect.ShowToast(message, type))
201+
}
202+
164203
companion object {
165204
private const val ALREADY_USED_INVITE_CODE_MESSAGE = "이미 사용된 초대 코드입니다."
166205
private const val INVALID_INVITE_CODE_MESSAGE = "유효하지 않은 초대 코드입니다."

0 commit comments

Comments
 (0)