Skip to content

Commit 0457953

Browse files
committed
[PC-1413] 바뀐 onboarding 적용 (#176)
* [PC-1413] lottie 의존성 추가 * [PC-1413] 바뀐 onboarding에 필요한 리소스 파일 추가 * [PC-1413] 바뀐 onboarding에 필요한 string 리소스 추가 * [PC-1413] 바뀐 OnboardingPageData, PageContentType * [PC-1413] StopAtProgressLottie 구현 * [PC-1413] PageContent 구현 * [PC-1413] 바뀐 onboarding ui 적용 - 스크롤 제거 - title fade in&out - 화면 전환 애니메메이션 제거 * [PC-1413] onboarding statusbar 색 변경 * [PC-1413] onboarding model에 @immutable 추가 * [PC-1413] StopAtProgressLottie 정지 로직 최적화 (derivedStateOf, snapshotFlow) * [PC-1413] OnboardingPageProvider 위치 이동하여 가독성 추가 * [PC-1413] 컴포즈 컴벤션에 맞게 파라미터 순서 수정 * [PC-1413] StopAtProgressLottie 로직 최적화 * [PC-1413] StopAtProgressLottie의 파라미터 targetProgress 제거 - lottie 이슈 해결되면 해당 로직 삭제 필요
1 parent 7af0387 commit 0457953

File tree

12 files changed

+318
-199
lines changed

12 files changed

+318
-199
lines changed
63.4 KB
Loading
12.7 KB
Binary file not shown.
855 KB
Binary file not shown.
24.9 KB
Binary file not shown.
22 KB
Binary file not shown.

core/designsystem/src/main/res/values/strings.xml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
<string name="profile_block_descriptioin">아직 프로필을 심사하고 있어요</string>
55

66
<!-- Onboarding -->
7-
<string name="skip">건너뛰기</string>
8-
<string name="start">시작하기</string>
9-
<string name="one_day_one_matching_title">하루 한 번,\n1:1로 만나는 특별한 인연</string>
10-
<string name="one_day_one_matching_description">매일 밤 10시, 새로운 매칭 조각이 도착해요.\n천천히 프로필을 살펴보고, 맞춰볼지 결정해보세요.</string>
11-
<string name="camera_block_title">안심하고\n소중한 만남을 즐기세요</string>
12-
<string name="camera_block_description">스크린샷은 제한되어 있어요.\n오직 이 공간에서만, 편안하게 인연을 찾아보세요.</string>
13-
7+
<string name="onboarding_basic_title">매일 밤 10시,\n가치관이 맞는 인연을\n이어줘요</string>
8+
<string name="onboarding_basic_button">인연을 더 만나려면?</string>
9+
<string name="onboarding_premium_title">원한다면, 퍼즐로\n더 많은 인연을\n이어갈 수 있어요</string>
10+
<string name="onboarding_premium_button">만남은 어떻게 이루어지나요?</string>
11+
<string name="onboarding_greenlight_title">상대가 먼저 다가오면\n‘그린라이트’,\n서로 통하면 만남이 시작돼요</string>
12+
<string name="onboarding_greenlight_button">나와 맞는 인연을 만나려면?</string>
13+
<string name="onboarding_talk_title">정성이 담긴 프로필은\n꼭 맞는 인연을 만날\n가능성이 높아요</string>
14+
<string name="onboarding_talk_button">안전하게 만나고 싶어요</string>
15+
<string name="onboarding_camera_block_title">스크린샷은 제한되어 있으니,\n안심하고 인연을 찾아보세요</string>
16+
<string name="onboarding_camera_block_button1">다시 볼래요</string>
17+
<string name="onboarding_camera_block_button2">시작할래요!</string>
18+
1419
<!-- Pause -->
1520
<string name="pause_title">📢 잠시 쉬어가는 안내 드려요</string>
1621
<string name="pause_description">피스를 이용해주시고, 함께해주셔서 정말 고맙습니다.\n피스의 iOS 버전 출시가 예상보다 지연되면서,\n안드로이드 서비스도 잠시 일시중지하기로 결정했습니다.</string>

feature/onboarding/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ plugins {
55
android {
66
namespace = "com.puzzle.onboarding"
77
}
8+
9+
dependencies {
10+
implementation(libs.lottie.compose)
11+
}

feature/onboarding/src/main/java/com/puzzle/onboarding/OnboardingScreen.kt

Lines changed: 74 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,15 @@ package com.puzzle.onboarding
22

33
import android.app.Activity
44
import androidx.activity.compose.BackHandler
5-
import androidx.compose.animation.AnimatedVisibility
6-
import androidx.compose.animation.fadeIn
7-
import androidx.compose.animation.fadeOut
8-
import androidx.compose.foundation.Image
95
import androidx.compose.foundation.background
10-
import androidx.compose.foundation.clickable
116
import androidx.compose.foundation.layout.Arrangement
127
import androidx.compose.foundation.layout.Column
138
import androidx.compose.foundation.layout.Row
14-
import androidx.compose.foundation.layout.Spacer
159
import androidx.compose.foundation.layout.fillMaxSize
1610
import androidx.compose.foundation.layout.fillMaxWidth
17-
import androidx.compose.foundation.layout.height
1811
import androidx.compose.foundation.layout.padding
19-
import androidx.compose.foundation.layout.size
2012
import androidx.compose.foundation.pager.HorizontalPager
2113
import androidx.compose.foundation.pager.rememberPagerState
22-
import androidx.compose.foundation.shape.CircleShape
23-
import androidx.compose.material3.Surface
24-
import androidx.compose.material3.Text
2514
import androidx.compose.runtime.Composable
2615
import androidx.compose.runtime.getValue
2716
import androidx.compose.runtime.mutableLongStateOf
@@ -30,22 +19,22 @@ import androidx.compose.runtime.rememberCoroutineScope
3019
import androidx.compose.runtime.setValue
3120
import androidx.compose.ui.Alignment
3221
import androidx.compose.ui.Modifier
33-
import androidx.compose.ui.draw.clip
3422
import androidx.compose.ui.platform.LocalContext
35-
import androidx.compose.ui.res.painterResource
3623
import androidx.compose.ui.res.stringResource
37-
import androidx.compose.ui.text.style.TextAlign
38-
import androidx.compose.ui.text.style.TextDecoration
39-
import androidx.compose.ui.tooling.preview.Preview
4024
import androidx.compose.ui.unit.dp
4125
import androidx.hilt.navigation.compose.hiltViewModel
4226
import com.puzzle.analytics.TrackScreenViewEvent
4327
import com.puzzle.common.event.PieceEvent
4428
import com.puzzle.common.ui.SnackBarState
4529
import com.puzzle.designsystem.R
30+
import com.puzzle.designsystem.component.PieceOutlinedButton
4631
import com.puzzle.designsystem.component.PieceSolidButton
32+
import com.puzzle.designsystem.component.PieceSubCloseTopBar
4733
import com.puzzle.designsystem.foundation.PieceTheme
4834
import com.puzzle.onboarding.contract.OnboardingIntent
35+
import com.puzzle.onboarding.model.OnboardingPageData
36+
import com.puzzle.onboarding.model.onboardingPages
37+
import com.puzzle.onboarding.ui.PageContent
4938
import kotlinx.coroutines.launch
5039

5140
@Composable
@@ -59,7 +48,7 @@ internal fun OnboardingRoute(viewModel: OnboardingViewModel = hiltViewModel()) {
5948
} else {
6049
viewModel.eventHelper.sendEvent(
6150
PieceEvent.ShowSnackBar(
62-
SnackBarState.TextOnly(context.getString(com.puzzle.designsystem.R.string.back_description))
51+
SnackBarState.TextOnly(context.getString(R.string.back_description))
6352
)
6453
)
6554
}
@@ -70,211 +59,104 @@ internal fun OnboardingRoute(viewModel: OnboardingViewModel = hiltViewModel()) {
7059
}
7160

7261
@Composable
73-
internal fun OnboardingScreen(onStartButtonClick: () -> Unit) {
62+
private fun OnboardingScreen(onStartButtonClick: () -> Unit) {
7463
val scope = rememberCoroutineScope()
64+
val pageCount = onboardingPages.size
65+
7566
val pagerState = rememberPagerState(
7667
initialPage = 0,
77-
pageCount = { 2 },
68+
pageCount = { pageCount },
7869
)
70+
val currentPage = pagerState.currentPage
71+
val currentPageData = onboardingPages.getOrNull(currentPage)
7972

80-
TrackScreenViewEvent(
81-
key = pagerState.currentPage,
82-
screenName = when (pagerState.currentPage) {
83-
0 -> "onboarding_dailymatch"
84-
1 -> "onboarding_safetynotice"
85-
else -> null
86-
}
87-
)
73+
currentPageData?.screenName?.let { screenName ->
74+
TrackScreenViewEvent(
75+
key = currentPage,
76+
screenName = screenName
77+
)
78+
}
8879

8980
Column(
9081
horizontalAlignment = Alignment.CenterHorizontally,
9182
modifier = Modifier
9283
.fillMaxSize()
93-
.padding(horizontal = 20.dp)
84+
.background(PieceTheme.colors.light3)
9485
) {
95-
OnboardingTopBar(
96-
currentPage = pagerState.currentPage,
97-
onSkipButtonClick = onStartButtonClick,
98-
modifier = Modifier.padding(bottom = 49.dp),
86+
87+
PieceSubCloseTopBar(
88+
title = "",
89+
contentColor = PieceTheme.colors.black,
90+
onCloseClick = onStartButtonClick,
91+
modifier = Modifier.padding(horizontal = 20.dp)
9992
)
10093

10194
HorizontalPager(
10295
state = pagerState,
103-
modifier = Modifier
104-
.weight(1f)
105-
.padding(bottom = 40.dp)
106-
) { page ->
107-
Column(modifier = Modifier.fillMaxSize()) {
108-
when (page) {
109-
0 -> OnboardingPageContent(
110-
imageRes = R.drawable.ic_onboarding_matching,
111-
title = stringResource(R.string.one_day_one_matching_title),
112-
description = stringResource(R.string.one_day_one_matching_description),
113-
)
96+
userScrollEnabled = false,
97+
modifier = Modifier.weight(1f)
98+
) { pageIndex ->
99+
100+
val isCurrentPage = pageIndex == currentPage
114101

115-
1 -> OnboardingPageContent(
116-
imageRes = R.drawable.ic_onboarding_camera,
117-
title = stringResource(R.string.camera_block_title),
118-
description = stringResource(R.string.camera_block_description),
119-
)
120-
}
102+
onboardingPages.getOrNull(pageIndex)?.let { data ->
103+
PageContent(
104+
titleRes = data.titleRes,
105+
pageContentType = data.contentType,
106+
isPageActive = isCurrentPage
107+
)
121108
}
122109
}
123110

124-
OnboardingIndicator(
125-
total = 2,
126-
current = pagerState.currentPage,
127-
modifier = Modifier
128-
.align(Alignment.CenterHorizontally)
129-
.padding(bottom = 30.dp),
130-
)
131-
132-
PieceSolidButton(
133-
label = when (pagerState.currentPage) {
134-
1 -> stringResource(R.string.start)
135-
else -> stringResource(R.string.next)
136-
},
137-
onClick = {
138-
when (pagerState.currentPage) {
139-
1 -> onStartButtonClick()
140-
else -> scope.launch { pagerState.animateScrollToPage(1) }
141-
}
142-
},
143-
modifier = Modifier
144-
.padding(bottom = 10.dp, top = 12.dp)
145-
.fillMaxWidth(),
111+
OnboardingBottomButton(
112+
currentPage = currentPage,
113+
pageCount = pageCount,
114+
currentPageData = currentPageData,
115+
onNextClick = { scope.launch { pagerState.scrollToPage(currentPage + 1) } },
116+
onStartClick = onStartButtonClick,
117+
onRestartClick = { scope.launch { pagerState.scrollToPage(0) } }
146118
)
147119
}
148120
}
149121

150122
@Composable
151-
private fun OnboardingTopBar(
123+
private fun OnboardingBottomButton(
152124
currentPage: Int,
153-
onSkipButtonClick: () -> Unit,
154-
modifier: Modifier = Modifier,
125+
pageCount: Int,
126+
currentPageData: OnboardingPageData?,
127+
onNextClick: () -> Unit,
128+
onStartClick: () -> Unit,
129+
onRestartClick: () -> Unit
155130
) {
156-
Row(
157-
verticalAlignment = Alignment.CenterVertically,
158-
modifier = modifier
159-
.fillMaxWidth()
160-
.height(64.dp),
161-
) {
162-
Image(
163-
painter = painterResource(R.drawable.ic_onboarding_logo),
164-
contentDescription = null,
165-
)
166-
167-
Spacer(modifier = Modifier.weight(1f))
168-
169-
AnimatedVisibility(
170-
visible = currentPage == 0,
171-
enter = fadeIn(),
172-
exit = fadeOut(),
131+
if (currentPage == pageCount - 1) {
132+
Row(
133+
modifier = Modifier
134+
.padding(horizontal = 20.dp)
135+
.padding(bottom = 10.dp, top = 12.dp)
136+
.fillMaxWidth(),
137+
horizontalArrangement = Arrangement.spacedBy(8.dp)
173138
) {
174-
Text(
175-
text = stringResource(R.string.skip),
176-
style = PieceTheme.typography.bodyMM.copy(
177-
textDecoration = TextDecoration.Underline
178-
),
179-
color = PieceTheme.colors.dark3,
180-
modifier = Modifier.clickable { onSkipButtonClick() }
139+
PieceOutlinedButton(
140+
label = stringResource(R.string.onboarding_camera_block_button1),
141+
onClick = onRestartClick,
142+
modifier = Modifier.weight(1f)
143+
)
144+
PieceSolidButton(
145+
label = stringResource(R.string.onboarding_camera_block_button2),
146+
onClick = onStartClick,
147+
modifier = Modifier.weight(1f)
181148
)
182149
}
183-
}
184-
}
150+
} else {
151+
val labelRes = currentPageData?.buttonLabelRes ?: return
185152

186-
@Composable
187-
private fun OnboardingPageContent(
188-
imageRes: Int,
189-
title: String,
190-
description: String
191-
) {
192-
Column(modifier = Modifier.fillMaxSize()) {
193-
Image(
194-
painter = painterResource(imageRes),
195-
contentDescription = null,
153+
PieceSolidButton(
154+
label = stringResource(labelRes),
155+
onClick = onNextClick,
196156
modifier = Modifier
197-
.align(Alignment.CenterHorizontally)
198-
.padding(bottom = 66.dp),
199-
)
200-
201-
Text(
202-
text = title,
203-
textAlign = TextAlign.Start,
204-
style = PieceTheme.typography.headingLSB,
205-
color = PieceTheme.colors.black,
206-
modifier = Modifier.padding(bottom = 12.dp),
207-
)
208-
209-
Text(
210-
text = description,
211-
textAlign = TextAlign.Start,
212-
style = PieceTheme.typography.bodySM,
213-
color = PieceTheme.colors.dark3,
214-
)
215-
}
216-
}
217-
218-
@Composable
219-
private fun OnboardingIndicator(
220-
total: Int,
221-
current: Int,
222-
modifier: Modifier = Modifier,
223-
) {
224-
Row(
225-
horizontalArrangement = Arrangement.spacedBy(8.dp),
226-
verticalAlignment = Alignment.CenterVertically,
227-
modifier = modifier
228-
) {
229-
(0 until total).forEachIndexed { index, _ ->
230-
if (index == current) {
231-
Spacer(
232-
modifier = Modifier
233-
.size(width = 20.dp, height = 8.dp)
234-
.clip(CircleShape)
235-
.background(PieceTheme.colors.dark2)
236-
)
237-
} else {
238-
Spacer(
239-
modifier = Modifier
240-
.size(8.dp)
241-
.clip(CircleShape)
242-
.background(PieceTheme.colors.light1)
243-
)
244-
}
245-
}
246-
}
247-
}
248-
249-
@Preview
250-
@Composable
251-
private fun OnboardingScreenPreview() {
252-
PieceTheme {
253-
Surface(
254-
color = PieceTheme.colors.white,
255-
modifier = Modifier.fillMaxSize(),
256-
) {
257-
OnboardingScreen {}
258-
}
259-
}
260-
}
261-
262-
@Preview
263-
@Composable
264-
private fun OnboardingTopBarPreview() {
265-
PieceTheme {
266-
OnboardingTopBar(
267-
currentPage = 0,
268-
onSkipButtonClick = {}
157+
.padding(horizontal = 20.dp)
158+
.padding(bottom = 10.dp, top = 12.dp)
159+
.fillMaxWidth(),
269160
)
270161
}
271-
}
272-
273-
274-
@Preview
275-
@Composable
276-
private fun OnboardingIndicatorPreview() {
277-
PieceTheme {
278-
OnboardingIndicator(total = 2, current = 0)
279-
}
280-
}
162+
}

0 commit comments

Comments
 (0)