Skip to content

Commit 692ee89

Browse files
author
새로운닉네임
committed
[BOOK-183] feat: 온보딩 화면 UI 구현 WIP
1 parent 1d183ca commit 692ee89

File tree

11 files changed

+374
-0
lines changed

11 files changed

+374
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.ninecraft.booket.core.designsystem
2+
3+
import androidx.annotation.DrawableRes
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.Alignment
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.painter.Painter
8+
import androidx.compose.ui.layout.ContentScale
9+
import com.skydoves.landscapist.ImageOptions
10+
import com.skydoves.landscapist.coil.CoilImage
11+
import com.skydoves.landscapist.components.rememberImageComponent
12+
import com.skydoves.landscapist.placeholder.placeholder.PlaceholderPlugin
13+
14+
@Composable
15+
fun ResourceImage(
16+
@DrawableRes imageRes: Int,
17+
contentDescription: String,
18+
modifier: Modifier = Modifier,
19+
placeholder: Painter? = null,
20+
contentScale: ContentScale = ContentScale.Crop,
21+
) {
22+
CoilImage(
23+
imageModel = { imageRes },
24+
modifier = modifier,
25+
component = rememberImageComponent {
26+
+PlaceholderPlugin.Loading(placeholder)
27+
+PlaceholderPlugin.Failure(placeholder)
28+
},
29+
imageOptions = ImageOptions(
30+
contentScale = contentScale,
31+
alignment = Alignment.Center,
32+
contentDescription = contentDescription,
33+
),
34+
previewPlaceholder = placeholder,
35+
)
36+
}

feature/onboarding/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")
2+
3+
plugins {
4+
alias(libs.plugins.booket.android.feature)
5+
alias(libs.plugins.kotlin.serialization)
6+
alias(libs.plugins.kotlin.parcelize)
7+
}
8+
9+
android {
10+
namespace = "com.ninecraft.booket.feature.onboarding"
11+
}
12+
13+
ksp {
14+
arg("circuit.codegen.mode", "hilt")
15+
}
16+
17+
dependencies {
18+
implementations(
19+
libs.logger,
20+
)
21+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
import androidx.compose.runtime.Composable
3+
import androidx.compose.runtime.rememberCoroutineScope
4+
import com.ninecraft.booket.feature.onboarding.OnboardingUiEvent
5+
import com.ninecraft.booket.feature.onboarding.OnboardingUiState
6+
import com.ninecraft.booket.feature.screens.HomeScreen
7+
import com.ninecraft.booket.feature.screens.OnboardingScreen
8+
import com.slack.circuit.codegen.annotations.CircuitInject
9+
import com.slack.circuit.runtime.Navigator
10+
import com.slack.circuit.runtime.presenter.Presenter
11+
import dagger.assisted.Assisted
12+
import dagger.assisted.AssistedFactory
13+
import dagger.assisted.AssistedInject
14+
import dagger.hilt.android.components.ActivityRetainedComponent
15+
16+
@Suppress("unused")
17+
class HomePresenter @AssistedInject constructor(
18+
@Assisted private val navigator: Navigator,
19+
) : Presenter<OnboardingUiState> {
20+
21+
@Composable
22+
override fun present(): OnboardingUiState {
23+
val scope = rememberCoroutineScope()
24+
25+
fun handleEvent(event: OnboardingUiEvent) {
26+
when (event) {
27+
is OnboardingUiEvent.OnNextButtonClick -> {
28+
navigator.resetRoot(HomeScreen)
29+
}
30+
}
31+
}
32+
33+
return OnboardingUiState(
34+
eventSink = ::handleEvent,
35+
)
36+
}
37+
38+
@CircuitInject(OnboardingScreen::class, ActivityRetainedComponent::class)
39+
@AssistedFactory
40+
fun interface Factory {
41+
fun create(navigator: Navigator): HomePresenter
42+
}
43+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package com.ninecraft.booket.feature.onboarding
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Spacer
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.height
8+
import androidx.compose.foundation.pager.HorizontalPager
9+
import androidx.compose.foundation.pager.rememberPagerState
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.res.stringResource
15+
import androidx.compose.ui.unit.dp
16+
import com.ninecraft.booket.core.designsystem.DevicePreview
17+
import com.ninecraft.booket.core.designsystem.ResourceImage
18+
import com.ninecraft.booket.core.designsystem.component.button.ReedButton
19+
import com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle
20+
import com.ninecraft.booket.core.designsystem.component.button.largeButtonStyle
21+
import com.ninecraft.booket.core.designsystem.theme.Black
22+
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
23+
import com.ninecraft.booket.core.ui.component.ReedFullScreen
24+
import com.ninecraft.booket.feature.onboarding.component.PagerIndicator
25+
26+
private const val ONBOARDING_STEPS_COUNT = 3
27+
28+
@Composable
29+
internal fun OnBoardingUi(
30+
setOnboardingCompletedStatus: (Boolean) -> Unit,
31+
modifier: Modifier = Modifier,
32+
) {
33+
val pagerState = rememberPagerState(pageCount = { ONBOARDING_STEPS_COUNT })
34+
35+
ReedFullScreen {
36+
Column(
37+
modifier = Modifier.fillMaxSize(),
38+
horizontalAlignment = Alignment.CenterHorizontally,
39+
) {
40+
HorizontalPager(
41+
state = pagerState,
42+
modifier = Modifier.fillMaxSize(),
43+
) { page ->
44+
when (page) {
45+
0 -> {
46+
Box(modifier = Modifier.fillMaxSize()) {
47+
Column(
48+
modifier = Modifier
49+
.fillMaxSize()
50+
.align(Alignment.Center),
51+
horizontalAlignment = Alignment.CenterHorizontally,
52+
) {
53+
Spacer(modifier = Modifier.height(98.dp))
54+
ResourceImage(
55+
imageRes = R.drawable.img_onboarding_second_graphic,
56+
contentDescription = "Onboarding First Graphic",
57+
)
58+
Text(
59+
text = stringResource(R.string.onboarding_first_step_title),
60+
color = Black,
61+
style = ReedTheme.typography.heading1Bold,
62+
)
63+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3))
64+
Text(
65+
text = stringResource(R.string.onboarding_first_step_description),
66+
color = ReedTheme.colors.contentSecondary,
67+
style = ReedTheme.typography.heading1Bold,
68+
)
69+
Spacer(modifier = Modifier.height(98.dp))
70+
}
71+
Column(modifier = Modifier.align(Alignment.BottomCenter)) {
72+
Spacer(modifier = Modifier.height(16.dp))
73+
PagerIndicator(
74+
pageCount = ONBOARDING_STEPS_COUNT,
75+
pagerState = pagerState,
76+
)
77+
ReedButton(
78+
onClick = { setOnboardingCompletedStatus(true) },
79+
text = stringResource(R.string.next),
80+
sizeStyle = largeButtonStyle,
81+
colorStyle = ReedButtonColorStyle.PRIMARY,
82+
)
83+
}
84+
}
85+
}
86+
87+
1 -> {
88+
Box(modifier = Modifier.fillMaxSize()) {
89+
Column(
90+
modifier = Modifier
91+
.fillMaxSize()
92+
.align(Alignment.Center),
93+
horizontalAlignment = Alignment.CenterHorizontally,
94+
) {
95+
Spacer(modifier = Modifier.height(98.dp))
96+
ResourceImage(
97+
imageRes = R.drawable.img_onboarding_second_graphic,
98+
contentDescription = "Onboarding First Graphic",
99+
)
100+
Text(
101+
text = stringResource(R.string.onboarding_second_step_title),
102+
color = Black,
103+
style = ReedTheme.typography.heading1Bold,
104+
)
105+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3))
106+
Text(
107+
text = stringResource(R.string.onboarding_second_step_description),
108+
color = ReedTheme.colors.contentSecondary,
109+
style = ReedTheme.typography.heading1Bold,
110+
)
111+
Spacer(modifier = Modifier.height(98.dp))
112+
}
113+
Column(modifier = Modifier.align(Alignment.BottomCenter)) {
114+
Spacer(modifier = Modifier.height(16.dp))
115+
PagerIndicator(
116+
pageCount = ONBOARDING_STEPS_COUNT,
117+
pagerState = pagerState,
118+
)
119+
ReedButton(
120+
onClick = { setOnboardingCompletedStatus(true) },
121+
text = stringResource(R.string.next),
122+
sizeStyle = largeButtonStyle,
123+
colorStyle = ReedButtonColorStyle.PRIMARY,
124+
)
125+
}
126+
}
127+
}
128+
129+
2 -> {
130+
Box(modifier = Modifier.fillMaxSize()) {
131+
Column(
132+
modifier = Modifier
133+
.fillMaxSize()
134+
.align(Alignment.Center),
135+
horizontalAlignment = Alignment.CenterHorizontally,
136+
) {
137+
Spacer(modifier = Modifier.height(98.dp))
138+
ResourceImage(
139+
imageRes = R.drawable.img_onboarding_second_graphic,
140+
contentDescription = "Onboarding First Graphic",
141+
)
142+
Text(
143+
text = stringResource(R.string.onboarding_third_step_title),
144+
color = Black,
145+
style = ReedTheme.typography.heading1Bold,
146+
)
147+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3))
148+
Text(
149+
text = stringResource(R.string.onboarding_third_step_description),
150+
color = ReedTheme.colors.contentSecondary,
151+
style = ReedTheme.typography.heading1Bold,
152+
)
153+
Spacer(modifier = Modifier.height(98.dp))
154+
}
155+
Column(modifier = Modifier.align(Alignment.BottomCenter)) {
156+
Spacer(modifier = Modifier.height(16.dp))
157+
PagerIndicator(
158+
pageCount = ONBOARDING_STEPS_COUNT,
159+
pagerState = pagerState,
160+
)
161+
ReedButton(
162+
onClick = { setOnboardingCompletedStatus(true) },
163+
text = stringResource(R.string.next),
164+
sizeStyle = largeButtonStyle,
165+
colorStyle = ReedButtonColorStyle.PRIMARY,
166+
)
167+
}
168+
}
169+
}
170+
}
171+
}
172+
}
173+
}
174+
}
175+
176+
@DevicePreview
177+
@Composable
178+
private fun OnBoardingScreenPreview() {
179+
ReedTheme {
180+
OnBoardingUi(
181+
setOnboardingCompletedStatus = {},
182+
)
183+
}
184+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.ninecraft.booket.feature.onboarding
2+
3+
import com.slack.circuit.runtime.CircuitUiEvent
4+
import com.slack.circuit.runtime.CircuitUiState
5+
6+
data class OnboardingUiState(
7+
val eventSink: (OnboardingUiEvent) -> Unit,
8+
) : CircuitUiState
9+
10+
sealed interface OnboardingUiEvent : CircuitUiEvent {
11+
data object OnNextButtonClick : OnboardingUiEvent
12+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.ninecraft.booket.feature.onboarding.component
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.pager.PagerState
12+
import androidx.compose.foundation.pager.rememberPagerState
13+
import androidx.compose.foundation.shape.CircleShape
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.ui.Alignment
16+
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.draw.clip
18+
import androidx.compose.ui.unit.dp
19+
import com.ninecraft.booket.core.designsystem.ComponentPreview
20+
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
21+
22+
@Composable
23+
internal fun PagerIndicator(
24+
pageCount: Int,
25+
pagerState: PagerState,
26+
modifier: Modifier = Modifier,
27+
) {
28+
Box(modifier = modifier) {
29+
Row(
30+
modifier = Modifier
31+
.height(32.dp)
32+
.fillMaxWidth()
33+
.align(Alignment.TopCenter),
34+
horizontalArrangement = Arrangement.Center,
35+
) {
36+
repeat(pageCount) { iteration ->
37+
val color = if (pagerState.currentPage == iteration) ReedTheme.colors.bgPrimary else ReedTheme.colors.bgSecondaryPressed
38+
39+
Box(
40+
modifier = Modifier
41+
.padding(horizontal = 8.dp)
42+
.clip(CircleShape)
43+
.background(color)
44+
.size(8.dp),
45+
)
46+
}
47+
}
48+
}
49+
}
50+
51+
@ComponentPreview
52+
@Composable
53+
private fun PagerIndicatorPreview() {
54+
val pageCount = 3
55+
val pagerState = rememberPagerState(pageCount = { pageCount })
56+
57+
ReedTheme {
58+
PagerIndicator(
59+
pageCount = 2,
60+
pagerState = pagerState,
61+
)
62+
}
63+
}
110 KB
Loading
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="next">다음</string>
4+
<string name="onboarding_first_step_title">읽고 있는 책을 등록하고 바로 기록해보세요</string>
5+
<string name="onboarding_first_step_description">책을 덮기 전, 마음에 남은 문장과 감정을 간편하게 남길 수 있어요</string>
6+
<string name="onboarding_second_step_title">어떻게 쓸지 막막할땐, 감상평 가이드가 도와드려요</string>
7+
<string name="onboarding_second_step_description">감정과 생각을 이끌어주는 문장들이 기록을 자연스럽게 도와줘요</string>
8+
<string name="onboarding_third_step_title">독서 중 느낀 감정은 씨앗으로 남겨보세요</string>
9+
<string name="onboarding_third_step_description">책마다 쌓인 감정들은 나만의 독서에 흔적이 됩니다</string>
10+
</resources>

feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ data class WebViewScreen(
4242

4343
@Parcelize
4444
data class BookDetailScreen(val isbn: String) : ReedScreen(name = "BookDetail()")
45+
46+
@Parcelize
47+
data object OnboardingScreen : ReedScreen(name = "Onboarding()")

0 commit comments

Comments
 (0)