Skip to content

Commit de8b84c

Browse files
authored
Merge pull request #241 from YAPP-Github/BOOK-479-feature/#240
feat: 도서 검색 실패시 대응(카카오톡 채널 문의 이동)
2 parents 25c1f6b + b065c0c commit de8b84c

13 files changed

Lines changed: 125 additions & 50 deletions

File tree

app/build.gradle.kts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")
22

3-
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
43
import com.google.devtools.ksp.gradle.KspExtension
4+
import com.ninecraft.booket.convention.getLocalProperty
55
import org.gradle.kotlin.dsl.configure
66
import java.util.Properties
77

@@ -32,6 +32,7 @@ android {
3232
getByName("debug") {
3333
isDebuggable = true
3434
applicationIdSuffix = ".dev"
35+
buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getLocalProperty("DEBUG_GOOGLE_WEB_CLIENT_ID"))
3536
manifestPlaceholders += mapOf(
3637
"appName" to "@string/app_name_dev",
3738
)
@@ -42,6 +43,7 @@ android {
4243
isMinifyEnabled = true
4344
isShrinkResources = true
4445
signingConfig = signingConfigs.getByName("release")
46+
buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getLocalProperty("RELEASE_GOOGLE_WEB_CLIENT_ID"))
4547
manifestPlaceholders += mapOf(
4648
"appName" to "@string/app_name",
4749
)
@@ -53,8 +55,8 @@ android {
5355
}
5456

5557
defaultConfig {
56-
buildConfigField("String", "KAKAO_NATIVE_APP_KEY", getApiKey("KAKAO_NATIVE_APP_KEY"))
57-
manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = getApiKey("KAKAO_NATIVE_APP_KEY").trim('"')
58+
buildConfigField("String", "KAKAO_NATIVE_APP_KEY", getLocalProperty("KAKAO_NATIVE_APP_KEY"))
59+
manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = getLocalProperty("KAKAO_NATIVE_APP_KEY").trim('"')
5860
}
5961

6062
buildFeatures {
@@ -109,7 +111,3 @@ dependencies {
109111
api(libs.circuit.codegen.annotation)
110112
ksp(libs.circuit.codegen.ksp)
111113
}
112-
113-
fun getApiKey(propertyKey: String): String {
114-
return gradleLocalProperties(rootDir, providers).getProperty(propertyKey)
115-
}

build-logic/src/main/kotlin/com/ninecraft/booket/convention/Extensions.kt

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

3+
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
34
import org.gradle.accessors.dm.LibrariesForLibs
45
import org.gradle.api.Project
56
import org.gradle.kotlin.dsl.the
@@ -10,3 +11,7 @@ internal val Project.libs
1011
internal fun Project.applyPlugins(vararg plugins: String) {
1112
plugins.forEach(pluginManager::apply)
1213
}
14+
15+
fun Project.getLocalProperty(propertyKey: String): String {
16+
return gradleLocalProperties(rootDir, providers).getProperty(propertyKey)
17+
}

core/network/build.gradle.kts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")
22

3-
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
3+
import com.ninecraft.booket.convention.getLocalProperty
44

55

66
plugins {
@@ -18,11 +18,11 @@ android {
1818

1919
buildTypes {
2020
debug {
21-
buildConfigField("String", "SERVER_BASE_URL", getServerBaseUrl("DEBUG_SERVER_BASE_URL"))
21+
buildConfigField("String", "SERVER_BASE_URL", getLocalProperty("DEBUG_SERVER_BASE_URL"))
2222
}
2323

2424
release {
25-
buildConfigField("String", "SERVER_BASE_URL", getServerBaseUrl("RELEASE_SERVER_BASE_URL"))
25+
buildConfigField("String", "SERVER_BASE_URL", getLocalProperty("RELEASE_SERVER_BASE_URL"))
2626
}
2727
}
2828
}
@@ -36,7 +36,3 @@ dependencies {
3636
libs.logger,
3737
)
3838
}
39-
40-
fun getServerBaseUrl(propertyKey: String): String {
41-
return gradleLocalProperties(rootDir, providers).getProperty(propertyKey)
42-
}

core/ocr/build.gradle.kts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")
22

3-
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
4-
3+
import com.ninecraft.booket.convention.getLocalProperty
54

65
plugins {
76
alias(libs.plugins.booket.android.library)
@@ -13,7 +12,7 @@ android {
1312
namespace = "com.ninecraft.booket.core.ocr"
1413

1514
defaultConfig {
16-
buildConfigField("String", "CLOUD_VISION_API_KEY", getApiKey("CLOUD_VISION_API_KEY"))
15+
buildConfigField("String", "CLOUD_VISION_API_KEY", getLocalProperty("CLOUD_VISION_API_KEY"))
1716
}
1817

1918
buildFeatures {
@@ -30,7 +29,3 @@ dependencies {
3029
libs.logger,
3130
)
3231
}
33-
34-
fun getApiKey(propertyKey: String): String {
35-
return gradleLocalProperties(rootDir, providers).getProperty(propertyKey)
36-
}

feature/login/build.gradle.kts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")
22

3+
import com.ninecraft.booket.convention.getLocalProperty
4+
35
plugins {
46
alias(libs.plugins.booket.android.feature)
57
alias(libs.plugins.kotlin.serialization)
@@ -8,6 +10,20 @@ plugins {
810

911
android {
1012
namespace = "com.ninecraft.booket.feature.login"
13+
14+
buildTypes {
15+
getByName("debug") {
16+
buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getLocalProperty("DEBUG_GOOGLE_WEB_CLIENT_ID"))
17+
}
18+
19+
getByName("release") {
20+
buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getLocalProperty("RELEASE_GOOGLE_WEB_CLIENT_ID"))
21+
}
22+
}
23+
24+
buildFeatures {
25+
buildConfig = true
26+
}
1127
}
1228

1329
dependencies {

feature/onboarding/stability/onboarding.stability

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ internal fun com.ninecraft.booket.feature.onboarding.OnboardingUi(state: com.nin
2525
- modifier: STABLE (marked @Stable or @Immutable)
2626

2727
@Composable
28-
internal fun com.ninecraft.booket.feature.onboarding.component.OnboardingPage(imageRes: kotlin.Int, titleRes: kotlin.Int, highlightTextRes: kotlin.Int, descriptionRes: kotlin.Int, modifier: androidx.compose.ui.Modifier): kotlin.Unit
28+
internal fun com.ninecraft.booket.feature.onboarding.component.OnboardingPage(imageRes: kotlin.Int, titleRes: kotlin.Int, highlightTextRes: kotlin.Int?, descriptionRes: kotlin.Int, modifier: androidx.compose.ui.Modifier): kotlin.Unit
2929
skippable: true
3030
restartable: true
3131
params:
3232
- imageRes: STABLE (primitive type)
3333
- titleRes: STABLE (primitive type)
34-
- highlightTextRes: STABLE (primitive type)
34+
- highlightTextRes: STABLE (class with no mutable properties)
3535
- descriptionRes: STABLE (primitive type)
3636
- modifier: STABLE (marked @Stable or @Immutable)
3737

feature/search/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")
22

3+
import com.ninecraft.booket.convention.getLocalProperty
4+
5+
36
plugins {
47
alias(libs.plugins.booket.android.feature)
58
alias(libs.plugins.kotlin.serialization)
@@ -8,6 +11,14 @@ plugins {
811

912
android {
1013
namespace = "com.ninecraft.booket.feature.search"
14+
15+
buildFeatures {
16+
buildConfig = true
17+
}
18+
19+
defaultConfig {
20+
buildConfigField("String", "REED_KAKAOTALK_CHANNEL_URL", getLocalProperty("REED_KAKAOTALK_CHANNEL_URL"))
21+
}
1122
}
1223

1324
dependencies {

feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class BookSearchPresenter(
6767
override fun present(): BookSearchUiState {
6868
val scope = rememberCoroutineScope()
6969
val userState by authRepository.userState.collectAsRetainedState(initial = UserState.Guest)
70-
var uiState by rememberRetained { mutableStateOf<UiState>(UiState.Idle) }
70+
var searchUiState by rememberRetained { mutableStateOf<SearchUiState>(SearchUiState.Idle) }
7171
var footerState by rememberRetained { mutableStateOf<FooterState>(FooterState.Idle) }
7272
val queryState = rememberTextFieldState()
7373
val recentSearches by repository.bookRecentSearches.collectAsRetainedState(initial = emptyList())
@@ -86,7 +86,7 @@ class BookSearchPresenter(
8686
fun searchBooks(query: String, startIndex: Int = START_INDEX) {
8787
scope.launch {
8888
if (startIndex == START_INDEX) {
89-
uiState = UiState.Loading
89+
searchUiState = SearchUiState.Loading
9090
} else {
9191
footerState = FooterState.Loading
9292
}
@@ -108,7 +108,7 @@ class BookSearchPresenter(
108108
isLastPage = result.lastPage
109109

110110
if (startIndex == START_INDEX) {
111-
uiState = UiState.Success
111+
searchUiState = SearchUiState.Success
112112
analyticsHelper.logEvent(SEARCH_BOOK_RESULT)
113113
} else {
114114
footerState = if (isLastPage) FooterState.End else FooterState.Idle
@@ -119,7 +119,7 @@ class BookSearchPresenter(
119119
analyticsHelper.logEvent(ERROR_SEARCH_LOADING)
120120
val errorMessage = exception.message ?: "알 수 없는 오류가 발생했습니다."
121121
if (startIndex == START_INDEX) {
122-
uiState = UiState.Error(exception)
122+
searchUiState = SearchUiState.Error(exception)
123123
} else {
124124
footerState = FooterState.Error(errorMessage)
125125
}
@@ -260,6 +260,10 @@ class BookSearchPresenter(
260260
is BookSearchUiEvent.OnBookRegisterSuccessCancelButtonClick -> {
261261
isBookRegisterSuccessBottomSheetVisible = false
262262
}
263+
264+
is BookSearchUiEvent.OnInquireClick -> {
265+
sideEffect = BookSearchSideEffect.NavigateToKakaoTalkChannel
266+
}
263267
}
264268
}
265269

@@ -268,7 +272,7 @@ class BookSearchPresenter(
268272
}
269273

270274
return BookSearchUiState(
271-
uiState = uiState,
275+
searchUiState = searchUiState,
272276
footerState = footerState,
273277
queryState = queryState,
274278
recentSearches = recentSearches.toImmutableList(),

feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import com.ninecraft.booket.core.common.constants.BookStatus
2424
import com.ninecraft.booket.core.common.extensions.toErrorType
2525
import com.ninecraft.booket.core.designsystem.DevicePreview
2626
import com.ninecraft.booket.core.designsystem.component.ReedDivider
27+
import com.ninecraft.booket.core.designsystem.component.button.ReedButton
28+
import com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle
29+
import com.ninecraft.booket.core.designsystem.component.button.smallButtonStyle
2730
import com.ninecraft.booket.core.designsystem.component.textfield.ReedTextField
2831
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
2932
import com.ninecraft.booket.core.designsystem.theme.White
@@ -114,19 +117,19 @@ internal fun BookSearchContent(
114117
ReedDivider()
115118
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
116119

117-
when (state.uiState) {
118-
is UiState.Loading -> {
120+
when (state.searchUiState) {
121+
is SearchUiState.Loading -> {
119122
ReedLoadingIndicator()
120123
}
121124

122-
is UiState.Error -> {
125+
is SearchUiState.Error -> {
123126
ReedErrorUi(
124-
errorType = state.uiState.exception.toErrorType(),
127+
errorType = state.searchUiState.exception.toErrorType(),
125128
onRetryClick = { state.eventSink(BookSearchUiEvent.OnRetryClick) },
126129
)
127130
}
128131

129-
is UiState.Idle -> {
132+
is SearchUiState.Idle -> {
130133
if (state.recentSearches.isEmpty()) {
131134
Box(
132135
modifier = Modifier.fillMaxSize(),
@@ -173,17 +176,33 @@ internal fun BookSearchContent(
173176
}
174177
}
175178

176-
is UiState.Success -> {
179+
is SearchUiState.Success -> {
177180
if (state.isEmptySearchResult) {
178181
Box(
179182
modifier = Modifier.fillMaxSize(),
180183
contentAlignment = Alignment.Center,
181184
) {
182-
Text(
183-
text = stringResource(R.string.empty_results),
184-
color = ReedTheme.colors.contentSecondary,
185-
style = ReedTheme.typography.body1Medium,
186-
)
185+
Column(
186+
horizontalAlignment = Alignment.CenterHorizontally,
187+
) {
188+
Text(
189+
text = stringResource(R.string.empty_results_title),
190+
color = ReedTheme.colors.contentPrimary,
191+
style = ReedTheme.typography.headline1SemiBold,
192+
)
193+
Text(
194+
text = stringResource(R.string.empty_results_description),
195+
color = ReedTheme.colors.contentSecondary,
196+
style = ReedTheme.typography.body1Medium,
197+
)
198+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4))
199+
ReedButton(
200+
onClick = { state.eventSink(BookSearchUiEvent.OnInquireClick) },
201+
text = stringResource(R.string.inquire),
202+
sizeStyle = smallButtonStyle,
203+
colorStyle = ReedButtonColorStyle.SECONDARY,
204+
)
205+
}
187206
}
188207
} else {
189208
Row(
@@ -294,7 +313,7 @@ internal fun BookSearchContent(
294313

295314
@DevicePreview
296315
@Composable
297-
private fun BookSearchPreview() {
316+
private fun BookRecentSearchPreview() {
298317
ReedTheme {
299318
BookSearchUi(
300319
state = BookSearchUiState(
@@ -303,3 +322,16 @@ private fun BookSearchPreview() {
303322
)
304323
}
305324
}
325+
326+
@DevicePreview
327+
@Composable
328+
private fun BookSearchEmptyResultPreview() {
329+
ReedTheme {
330+
BookSearchContent(
331+
state = BookSearchUiState(
332+
searchUiState = SearchUiState.Success,
333+
eventSink = {},
334+
),
335+
)
336+
}
337+
}

feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUiState.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import kotlinx.collections.immutable.persistentListOf
1414
import java.util.UUID
1515

1616
@Immutable
17-
sealed interface UiState {
18-
data object Idle : UiState
19-
data object Loading : UiState
20-
data object Success : UiState
21-
data class Error(val exception: Throwable) : UiState
17+
sealed interface SearchUiState {
18+
data object Idle : SearchUiState
19+
data object Loading : SearchUiState
20+
data object Success : SearchUiState
21+
data class Error(val exception: Throwable) : SearchUiState
2222
}
2323

2424
data class BookSearchUiState(
25-
val uiState: UiState = UiState.Idle,
25+
val searchUiState: SearchUiState = SearchUiState.Idle,
2626
val footerState: FooterState = FooterState.Idle,
2727
val queryState: TextFieldState = TextFieldState(),
2828
val recentSearches: ImmutableList<String> = persistentListOf(),
@@ -37,7 +37,7 @@ data class BookSearchUiState(
3737
val sideEffect: BookSearchSideEffect? = null,
3838
val eventSink: (BookSearchUiEvent) -> Unit,
3939
) : CircuitUiState {
40-
val isEmptySearchResult: Boolean get() = uiState is UiState.Success && searchResult.totalResults == 0
40+
val isEmptySearchResult: Boolean get() = searchUiState is SearchUiState.Success && searchResult.totalResults == 0
4141
}
4242

4343
@Immutable
@@ -46,6 +46,8 @@ sealed interface BookSearchSideEffect {
4646
val message: UiText,
4747
private val key: String = UUID.randomUUID().toString(),
4848
) : BookSearchSideEffect
49+
50+
data object NavigateToKakaoTalkChannel : BookSearchSideEffect
4951
}
5052

5153
sealed interface BookSearchUiEvent : CircuitUiEvent {
@@ -64,4 +66,5 @@ sealed interface BookSearchUiEvent : CircuitUiEvent {
6466
data object OnBookRegisterButtonClick : BookSearchUiEvent
6567
data object OnBookRegisterSuccessOkButtonClick : BookSearchUiEvent
6668
data object OnBookRegisterSuccessCancelButtonClick : BookSearchUiEvent
69+
data object OnInquireClick : BookSearchUiEvent
6770
}

0 commit comments

Comments
 (0)