Skip to content

Commit 4c358da

Browse files
authored
Merge pull request #35 from YAPP-Github/BOOK-112-feature/#33
feat: 약관 동의 화면 UI 구현
2 parents ce5ebd8 + c047329 commit 4c358da

File tree

5 files changed

+299
-8
lines changed

5 files changed

+299
-8
lines changed

feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.compose.runtime.mutableStateOf
66
import androidx.compose.runtime.rememberCoroutineScope
77
import androidx.compose.runtime.setValue
88
import com.ninecraft.booket.core.data.api.repository.AuthRepository
9-
import com.ninecraft.booket.feature.home.HomeScreen
109
import com.orhanobut.logger.Logger
1110
import com.slack.circuit.codegen.annotations.CircuitInject
1211
import com.slack.circuit.retained.rememberRetained
@@ -51,7 +50,7 @@ class LoginPresenter @AssistedInject constructor(
5150
repository.login(event.accessToken)
5251
.onSuccess { result ->
5352
repository.saveTokens(result.accessToken, result.refreshToken)
54-
navigator.resetRoot(HomeScreen)
53+
navigator.goTo(TermsAgreementScreen)
5554
}.onFailure { exception ->
5655
exception.message?.let { Logger.e(it) }
5756
sideEffect = exception.message?.let {

feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginScreen.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.ninecraft.booket.feature.login
22

3+
import androidx.compose.foundation.background
34
import androidx.compose.foundation.layout.Arrangement
45
import androidx.compose.foundation.layout.Box
56
import androidx.compose.foundation.layout.Column
67
import androidx.compose.foundation.layout.fillMaxSize
78
import androidx.compose.foundation.layout.fillMaxWidth
8-
import androidx.compose.foundation.layout.height
99
import androidx.compose.foundation.layout.padding
1010
import androidx.compose.material3.CircularProgressIndicator
1111
import androidx.compose.material3.Icon
@@ -17,12 +17,12 @@ import androidx.compose.ui.graphics.Color
1717
import androidx.compose.ui.graphics.vector.ImageVector
1818
import androidx.compose.ui.res.stringResource
1919
import androidx.compose.ui.res.vectorResource
20-
import androidx.compose.ui.unit.dp
2120
import com.ninecraft.booket.core.designsystem.DevicePreview
2221
import com.ninecraft.booket.core.designsystem.component.button.ReedButton
2322
import com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle
2423
import com.ninecraft.booket.core.designsystem.component.button.largeButtonStyle
2524
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
25+
import com.ninecraft.booket.core.designsystem.theme.White
2626
import com.slack.circuit.codegen.annotations.CircuitInject
2727
import com.slack.circuit.runtime.CircuitUiEvent
2828
import com.slack.circuit.runtime.CircuitUiState
@@ -63,7 +63,9 @@ internal fun Login(
6363
)
6464

6565
Column(
66-
modifier = modifier.fillMaxSize(),
66+
modifier = modifier
67+
.fillMaxSize()
68+
.background(White),
6769
horizontalAlignment = Alignment.CenterHorizontally,
6870
verticalArrangement = Arrangement.Center,
6971
) {
@@ -78,8 +80,11 @@ internal fun Login(
7880
},
7981
modifier = Modifier
8082
.fillMaxWidth()
81-
.padding(start = 32.dp, end = 32.dp, bottom = 32.dp)
82-
.height(56.dp)
83+
.padding(
84+
start = ReedTheme.spacing.spacing5,
85+
end = ReedTheme.spacing.spacing5,
86+
bottom = ReedTheme.spacing.spacing8,
87+
)
8388
.align(Alignment.BottomCenter),
8489
colorStyle = ReedButtonColorStyle.KAKAO,
8590
sizeStyle = largeButtonStyle,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.ninecraft.booket.feature.login
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.derivedStateOf
5+
import androidx.compose.runtime.getValue
6+
import androidx.compose.runtime.mutableStateOf
7+
import androidx.compose.runtime.remember
8+
import androidx.compose.runtime.setValue
9+
import com.ninecraft.booket.feature.home.HomeScreen
10+
import com.slack.circuit.codegen.annotations.CircuitInject
11+
import com.slack.circuit.retained.rememberRetained
12+
import com.slack.circuit.runtime.Navigator
13+
import com.slack.circuit.runtime.presenter.Presenter
14+
import dagger.assisted.Assisted
15+
import dagger.assisted.AssistedFactory
16+
import dagger.assisted.AssistedInject
17+
import dagger.hilt.android.components.ActivityRetainedComponent
18+
import kotlinx.collections.immutable.persistentListOf
19+
import kotlinx.collections.immutable.toPersistentList
20+
21+
class TermsAgreementPresenter @AssistedInject constructor(
22+
@Assisted private val navigator: Navigator,
23+
) : Presenter<TermsAgreementScreen.State> {
24+
25+
@Composable
26+
override fun present(): TermsAgreementScreen.State {
27+
var agreedTerms by rememberRetained {
28+
mutableStateOf(persistentListOf(false, false, false))
29+
}
30+
31+
val isAllAgreed by remember {
32+
derivedStateOf {
33+
agreedTerms.all { it }
34+
}
35+
}
36+
37+
fun handleEvent(event: TermsAgreementScreen.Event) {
38+
when (event) {
39+
is TermsAgreementScreen.Event.OnAllTermsAgreedClick -> {
40+
val toggleAgreed = !isAllAgreed
41+
agreedTerms = agreedTerms.map { toggleAgreed }.toPersistentList()
42+
}
43+
44+
is TermsAgreementScreen.Event.OnTermItemClick -> {
45+
agreedTerms = agreedTerms.set(event.index, !agreedTerms[event.index])
46+
}
47+
48+
is TermsAgreementScreen.Event.OnBackClick -> {
49+
navigator.pop()
50+
}
51+
52+
is TermsAgreementScreen.Event.OnTermDetailClick -> {
53+
// TODO: 웹뷰 화면으로 이동
54+
}
55+
56+
is TermsAgreementScreen.Event.OnStartButtonClick -> {
57+
navigator.resetRoot(HomeScreen)
58+
}
59+
}
60+
}
61+
62+
return TermsAgreementScreen.State(
63+
isAllAgreed = isAllAgreed,
64+
agreedTerms = agreedTerms,
65+
eventSink = ::handleEvent,
66+
)
67+
}
68+
69+
@CircuitInject(TermsAgreementScreen::class, ActivityRetainedComponent::class)
70+
@AssistedFactory
71+
fun interface Factory {
72+
fun create(navigator: Navigator): TermsAgreementPresenter
73+
}
74+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package com.ninecraft.booket.feature.login
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.height
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.foundation.layout.width
13+
import androidx.compose.foundation.shape.RoundedCornerShape
14+
import androidx.compose.material3.Icon
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.ui.Alignment
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.graphics.vector.ImageVector
21+
import androidx.compose.ui.res.stringArrayResource
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.res.vectorResource
24+
import androidx.compose.ui.unit.dp
25+
import com.ninecraft.booket.core.common.extensions.clickableSingle
26+
import com.ninecraft.booket.core.designsystem.DevicePreview
27+
import com.ninecraft.booket.core.designsystem.component.appbar.ReedBackTopAppBar
28+
import com.ninecraft.booket.core.designsystem.component.button.ReedButton
29+
import com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle
30+
import com.ninecraft.booket.core.designsystem.component.button.largeButtonStyle
31+
import com.ninecraft.booket.core.designsystem.component.checkbox.SquareCheckBox
32+
import com.ninecraft.booket.core.designsystem.component.checkbox.TickOnlyCheckBox
33+
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
34+
import com.ninecraft.booket.core.designsystem.theme.White
35+
import com.slack.circuit.codegen.annotations.CircuitInject
36+
import com.slack.circuit.runtime.CircuitUiEvent
37+
import com.slack.circuit.runtime.CircuitUiState
38+
import com.slack.circuit.runtime.screen.Screen
39+
import dagger.hilt.android.components.ActivityRetainedComponent
40+
import kotlinx.collections.immutable.ImmutableList
41+
import kotlinx.collections.immutable.persistentListOf
42+
import kotlinx.parcelize.Parcelize
43+
44+
@Parcelize
45+
data object TermsAgreementScreen : Screen {
46+
data class State(
47+
val isAllAgreed: Boolean,
48+
val agreedTerms: ImmutableList<Boolean>,
49+
val eventSink: (Event) -> Unit,
50+
) : CircuitUiState
51+
52+
sealed interface Event : CircuitUiEvent {
53+
data object OnAllTermsAgreedClick : Event
54+
data class OnTermItemClick(val index: Int) : Event
55+
data object OnBackClick : Event
56+
data class OnTermDetailClick(val url: String) : Event
57+
data object OnStartButtonClick : Event
58+
}
59+
}
60+
61+
@CircuitInject(TermsAgreementScreen::class, ActivityRetainedComponent::class)
62+
@Composable
63+
internal fun TermsAgreement(
64+
state: TermsAgreementScreen.State,
65+
modifier: Modifier = Modifier,
66+
) {
67+
Column(
68+
modifier = modifier
69+
.fillMaxSize()
70+
.background(White),
71+
) {
72+
ReedBackTopAppBar(
73+
onNavigateBack = {
74+
state.eventSink(TermsAgreementScreen.Event.OnBackClick)
75+
},
76+
)
77+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
78+
Column(
79+
modifier = Modifier
80+
.weight(1f)
81+
.padding(horizontal = ReedTheme.spacing.spacing5),
82+
) {
83+
Text(
84+
text = stringResource(R.string.terms_agreement_title),
85+
color = ReedTheme.colors.contentPrimary,
86+
style = ReedTheme.typography.title2SemiBold,
87+
)
88+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4))
89+
Row(
90+
modifier = Modifier
91+
.fillMaxWidth()
92+
.border(
93+
width = 1.dp,
94+
color = ReedTheme.colors.contentBrand,
95+
shape = RoundedCornerShape(ReedTheme.radius.sm),
96+
)
97+
.padding(
98+
horizontal = ReedTheme.spacing.spacing4,
99+
vertical = ReedTheme.spacing.spacing5,
100+
),
101+
verticalAlignment = Alignment.CenterVertically,
102+
) {
103+
SquareCheckBox(
104+
checked = state.isAllAgreed,
105+
onCheckedChange = {
106+
state.eventSink(TermsAgreementScreen.Event.OnAllTermsAgreedClick)
107+
},
108+
)
109+
Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing4))
110+
Text(
111+
text = stringResource(R.string.terms_agreement_all),
112+
color = ReedTheme.colors.contentPrimary,
113+
style = ReedTheme.typography.headline1SemiBold,
114+
)
115+
}
116+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4))
117+
118+
val termsTitles = stringArrayResource(id = R.array.terms_agreement_items)
119+
120+
termsTitles.forEachIndexed { index, title ->
121+
TermItem(
122+
title = title,
123+
checked = state.agreedTerms[index],
124+
onCheckClick = {
125+
state.eventSink(TermsAgreementScreen.Event.OnTermItemClick(index))
126+
},
127+
onDetailClick = {
128+
state.eventSink(TermsAgreementScreen.Event.OnTermDetailClick(""))
129+
},
130+
)
131+
}
132+
}
133+
ReedButton(
134+
onClick = {
135+
state.eventSink(TermsAgreementScreen.Event.OnStartButtonClick)
136+
},
137+
modifier = Modifier
138+
.fillMaxWidth()
139+
.padding(
140+
start = ReedTheme.spacing.spacing5,
141+
end = ReedTheme.spacing.spacing5,
142+
bottom = ReedTheme.spacing.spacing4,
143+
),
144+
colorStyle = ReedButtonColorStyle.PRIMARY,
145+
sizeStyle = largeButtonStyle,
146+
enabled = state.isAllAgreed,
147+
text = stringResource(R.string.terms_agreement_button_start),
148+
)
149+
}
150+
}
151+
152+
@Composable
153+
private fun TermItem(
154+
title: String,
155+
modifier: Modifier = Modifier,
156+
checked: Boolean = false,
157+
onCheckClick: () -> Unit = {},
158+
onDetailClick: () -> Unit = {},
159+
) {
160+
Row(
161+
modifier = modifier
162+
.fillMaxWidth()
163+
.clickableSingle {
164+
onDetailClick()
165+
}
166+
.padding(
167+
start = ReedTheme.spacing.spacing5,
168+
end = ReedTheme.spacing.spacing3,
169+
top = ReedTheme.spacing.spacing2,
170+
bottom = ReedTheme.spacing.spacing2,
171+
),
172+
verticalAlignment = Alignment.CenterVertically,
173+
) {
174+
TickOnlyCheckBox(
175+
checked = checked,
176+
onCheckedChange = { onCheckClick() },
177+
)
178+
Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing1))
179+
Text(
180+
text = title,
181+
modifier = Modifier.weight(1f),
182+
color = ReedTheme.colors.contentPrimary,
183+
style = ReedTheme.typography.body1Medium,
184+
)
185+
Icon(
186+
imageVector = ImageVector.vectorResource(id = com.ninecraft.booket.core.designsystem.R.drawable.ic_chevron_right),
187+
contentDescription = "Navigation Icon",
188+
tint = Color.Unspecified,
189+
)
190+
}
191+
}
192+
193+
@DevicePreview
194+
@Composable
195+
private fun TermsAgreementPreview() {
196+
ReedTheme {
197+
TermsAgreement(
198+
state = TermsAgreementScreen.State(
199+
isAllAgreed = false,
200+
agreedTerms = persistentListOf(false, false, false),
201+
eventSink = {},
202+
),
203+
)
204+
}
205+
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<string name="kakao_login">카카오 로그인</string>
3+
<string name="kakao_login">카카오로 시작하기</string>
4+
<string name="terms_agreement_title">약관 동의 후\n독서 기록을 남겨보세요</string>
5+
<string name="terms_agreement_all">약관 전체 동의</string>
6+
<string name="terms_agreement_button_start">시작하기</string>
7+
<string-array name="terms_agreement_items">
8+
<item>(필수)서비스 이용약관</item>
9+
<item>(필수)개인정보처리방침</item>
10+
<item>(필수)만 14세 이상입니다</item>
11+
</string-array>
412
</resources>

0 commit comments

Comments
 (0)