Skip to content

Commit 5eb114c

Browse files
authored
Merge pull request #88 from YAPP-Github/BOOK-188-feature/#81
feat: 도서 상세 화면 Ui 구현
2 parents 29c9b14 + 070b7a2 commit 5eb114c

43 files changed

Lines changed: 1449 additions & 128 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/common/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ dependencies {
1616
projects.core.model,
1717
projects.core.network,
1818

19+
libs.kotlinx.collections.immutable,
20+
1921
libs.logger,
2022
)
2123
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.ninecraft.booket.core.common.constants
2+
3+
import com.ninecraft.booket.core.common.R
4+
5+
enum class BookStatus(val value: String) {
6+
BEFORE_READING("BEFORE_READING"),
7+
READING("READING"),
8+
COMPLETED("COMPLETED"),
9+
;
10+
11+
fun getDisplayNameRes(): Int {
12+
return when (this) {
13+
BEFORE_READING -> R.string.book_status_before
14+
READING -> R.string.book_status_reading
15+
COMPLETED -> R.string.book_status_completed
16+
}
17+
}
18+
19+
companion object Companion {
20+
fun fromValue(value: String): BookStatus? {
21+
return entries.find { it.value == value }
22+
}
23+
}
24+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.ninecraft.booket.core.common.extensions
2+
3+
import androidx.compose.ui.graphics.Color
4+
import com.ninecraft.booket.core.model.Emotion
5+
6+
fun Emotion.toTextColor(): Color {
7+
return when (this) {
8+
Emotion.WARM -> Color(0xFFE3931B)
9+
Emotion.JOY -> Color(0xFFEE6B33)
10+
Emotion.TENSION -> Color(0xFF9A55E4)
11+
Emotion.SADNESS -> Color(0xFF2872E9)
12+
}
13+
}
14+
15+
fun Emotion.toBackgroundColor(): Color {
16+
return when (this) {
17+
Emotion.WARM -> Color(0xFFFFF5D3)
18+
Emotion.JOY -> Color(0xFFFFEBE3)
19+
Emotion.TENSION -> Color(0xFFF3E8FF)
20+
Emotion.SADNESS -> Color(0xFFE1ECFF)
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.ninecraft.booket.core.common.util
2+
3+
import com.ninecraft.booket.core.model.EmotionModel
4+
5+
data class EmotionAnalysisResult(
6+
val topEmotions: List<EmotionModel>,
7+
val displayType: EmotionDisplayType,
8+
)
9+
10+
enum class EmotionDisplayType {
11+
SINGLE, // 1개 감정이 1위
12+
DUAL, // 2개 감정이 공동 1위
13+
BALANCED, // 3개 이상 감정이 공동 1위
14+
}
15+
16+
fun analyzeEmotions(emotions: List<EmotionModel>): EmotionAnalysisResult {
17+
if (emotions.isEmpty()) {
18+
return EmotionAnalysisResult(emptyList(), EmotionDisplayType.BALANCED)
19+
}
20+
21+
val maxCount = emotions.maxOf { it.count }
22+
val topEmotions = emotions.filter { it.count == maxCount }
23+
24+
val displayType = when (topEmotions.size) {
25+
1 -> EmotionDisplayType.SINGLE
26+
2 -> EmotionDisplayType.DUAL
27+
else -> EmotionDisplayType.BALANCED
28+
}
29+
30+
return EmotionAnalysisResult(topEmotions, displayType)
31+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="book_status_before">읽기 전</string>
4+
<string name="book_status_reading">읽는 중</string>
5+
<string name="book_status_completed">독서 완료</string>
6+
<string name="record_sort_page_ascending">페이지순</string>
7+
<string name="record_sort_recent_register">최신 등록순</string>
8+
</resources>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.ninecraft.booket.core.model
2+
3+
import androidx.compose.runtime.Stable
4+
5+
@Stable
6+
data class EmotionModel(
7+
val type: Emotion,
8+
val count: Int,
9+
)
10+
11+
enum class Emotion(
12+
val displayName: String,
13+
) {
14+
WARM("따뜻함"),
15+
JOY("즐거움"),
16+
TENSION("긴장감"),
17+
SADNESS("슬픔"),
18+
}

core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkModule.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ import javax.inject.Singleton
2424
private const val MaxTimeoutMillis = 15_000L
2525

2626
private val jsonRule = Json {
27+
// 기본값도 JSON에 포함하여 직렬화
2728
encodeDefaults = true
29+
// JSON에 정의되지 않은 키는 무시 (역직렬화 시 에러 방지)
2830
ignoreUnknownKeys = true
31+
// JSON을 보기 좋게 들여쓰기하여 포맷팅
2932
prettyPrint = true
33+
// 엄격하지 않은 파싱 (따옴표 없는 키, 후행 쉼표 등 허용)
3034
isLenient = true
3135
}
3236

core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/LoadStateFooter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.material3.Button
1010
import androidx.compose.material3.CircularProgressIndicator
1111
import androidx.compose.material3.Text
1212
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.Immutable
1314
import androidx.compose.ui.Alignment
1415
import androidx.compose.ui.Modifier
1516
import androidx.compose.ui.res.stringResource
@@ -68,6 +69,7 @@ fun LoadStateFooter(
6869
}
6970
}
7071

72+
@Immutable
7173
sealed interface FooterState {
7274
data object Idle : FooterState
7375
data object Loading : FooterState

feature/detail/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ ksp {
1616

1717
dependencies {
1818
implementations(
19+
libs.kotlinx.collections.immutable,
20+
1921
libs.logger,
2022
)
2123
}

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import androidx.compose.runtime.getValue
55
import androidx.compose.runtime.mutableStateOf
66
import androidx.compose.runtime.rememberCoroutineScope
77
import androidx.compose.runtime.setValue
8+
import com.ninecraft.booket.core.common.constants.BookStatus
89
import com.ninecraft.booket.core.common.utils.handleException
910
import com.ninecraft.booket.core.data.api.repository.BookRepository
1011
import com.ninecraft.booket.feature.screens.BookDetailScreen
1112
import com.ninecraft.booket.feature.screens.LoginScreen
13+
import com.ninecraft.booket.feature.screens.RecordDetailScreen
14+
import com.ninecraft.booket.feature.screens.RecordScreen
1215
import com.orhanobut.logger.Logger
1316
import com.slack.circuit.codegen.annotations.CircuitInject
1417
import com.slack.circuit.retained.rememberRetained
@@ -30,6 +33,10 @@ class BookDetailPresenter @AssistedInject constructor(
3033
override fun present(): BookDetailUiState {
3134
val scope = rememberCoroutineScope()
3235

36+
var isBookUpdateBottomSheetVisible by rememberRetained { mutableStateOf(false) }
37+
var isRecordSortBottomSheetVisible by rememberRetained { mutableStateOf(false) }
38+
var currentBookStatus by rememberRetained { mutableStateOf(BookStatus.READING) }
39+
var currentRecordSort by rememberRetained { mutableStateOf(RecordSort.PAGE_ASCENDING) }
3340
var sideEffect by rememberRetained { mutableStateOf<BookDetailSideEffect?>(null) }
3441

3542
fun upsertBook(bookIsbn: String, bookStatus: String) {
@@ -57,29 +64,58 @@ class BookDetailPresenter @AssistedInject constructor(
5764

5865
fun handleEvent(event: BookDetailUiEvent) {
5966
when (event) {
60-
BookDetailUiEvent.InitSideEffect -> {
67+
is BookDetailUiEvent.InitSideEffect -> {
6168
sideEffect = null
6269
}
6370

64-
BookDetailUiEvent.OnBackClicked -> {
71+
is BookDetailUiEvent.OnBackClick -> {
6572
navigator.pop()
6673
}
6774

68-
BookDetailUiEvent.OnBeforeReadingClick -> {
69-
upsertBook(screen.isbn, "BEFORE_READING")
75+
is BookDetailUiEvent.OnBookStatusButtonClick -> {
76+
isBookUpdateBottomSheetVisible = true
7077
}
7178

72-
BookDetailUiEvent.OnReadingClick -> {
73-
upsertBook(screen.isbn, "READING")
79+
is BookDetailUiEvent.OnRegisterRecordButtonClick -> {
80+
navigator.goTo(RecordScreen(""))
7481
}
7582

76-
BookDetailUiEvent.OnCompletedClick -> {
77-
upsertBook(screen.isbn, "COMPLETED")
83+
is BookDetailUiEvent.OnRecordSortButtonClick -> {
84+
isRecordSortBottomSheetVisible = true
85+
}
86+
87+
is BookDetailUiEvent.OnBookUpdateBottomSheetDismiss -> {
88+
isBookUpdateBottomSheetVisible = false
89+
}
90+
91+
is BookDetailUiEvent.OnBookStatusItemSelected -> {
92+
currentBookStatus = event.bookStatus
93+
}
94+
95+
is BookDetailUiEvent.OnBookStatusUpdateButtonClick -> {
96+
upsertBook(screen.isbn, currentBookStatus.value)
97+
}
98+
99+
is BookDetailUiEvent.OnRecordSortBottomSheetDismiss -> {
100+
isRecordSortBottomSheetVisible = false
101+
}
102+
103+
is BookDetailUiEvent.OnRecordSortItemSelected -> {
104+
currentRecordSort = event.sortType
105+
isRecordSortBottomSheetVisible = false
106+
}
107+
108+
is BookDetailUiEvent.OnRecordItemClick -> {
109+
navigator.goTo(RecordDetailScreen(event.recordId))
78110
}
79111
}
80112
}
81113

82114
return BookDetailUiState(
115+
isBookUpdateBottomSheetVisible = isBookUpdateBottomSheetVisible,
116+
isRecordSortBottomSheetVisible = isRecordSortBottomSheetVisible,
117+
currentBookStatus = currentBookStatus,
118+
currentRecordSort = currentRecordSort,
83119
sideEffect = sideEffect,
84120
eventSink = ::handleEvent,
85121
)

0 commit comments

Comments
 (0)