Skip to content

Commit 44805e4

Browse files
authored
Merge pull request #90 from YAPP-Github/BOOK-169-feature/#71
feat: 홈 화면 API 연동
2 parents 667f63e + dfd8dbe commit 44805e4

File tree

13 files changed

+253
-60
lines changed

13 files changed

+253
-60
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.ninecraft.booket.core.data.api.repository
2+
3+
import com.ninecraft.booket.core.model.HomeModel
4+
5+
interface HomeRepository {
6+
suspend fun getHome(): Result<HomeModel>
7+
}

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/RepositoryModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package com.ninecraft.booket.core.data.impl.di
22

33
import com.ninecraft.booket.core.data.api.repository.AuthRepository
44
import com.ninecraft.booket.core.data.api.repository.BookRepository
5+
import com.ninecraft.booket.core.data.api.repository.HomeRepository
56
import com.ninecraft.booket.core.data.api.repository.RecordRepository
67
import com.ninecraft.booket.core.data.api.repository.UserRepository
78
import com.ninecraft.booket.core.data.impl.repository.DefaultAuthRepository
89
import com.ninecraft.booket.core.data.impl.repository.DefaultBookRepository
10+
import com.ninecraft.booket.core.data.impl.repository.DefaultHomeRepository
911
import com.ninecraft.booket.core.data.impl.repository.DefaultRecordRepository
1012
import com.ninecraft.booket.core.data.impl.repository.DefaultUserRepository
1113
import dagger.Binds
@@ -33,4 +35,8 @@ internal abstract class RepositoryModule {
3335
@Binds
3436
@Singleton
3537
abstract fun bindRecordRepository(defaultRecordRepository: DefaultRecordRepository): RecordRepository
38+
39+
@Binds
40+
@Singleton
41+
abstract fun bindHomeRepository(defaultHomeRepository: DefaultHomeRepository): HomeRepository
3642
}

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@ import com.ninecraft.booket.core.model.BookDetailModel
55
import com.ninecraft.booket.core.model.BookSearchModel
66
import com.ninecraft.booket.core.model.BookSummaryModel
77
import com.ninecraft.booket.core.model.BookUpsertModel
8+
import com.ninecraft.booket.core.model.HomeModel
89
import com.ninecraft.booket.core.model.LibraryBookSummaryModel
910
import com.ninecraft.booket.core.model.LibraryBooksModel
1011
import com.ninecraft.booket.core.model.LibraryModel
1112
import com.ninecraft.booket.core.model.PageInfoModel
13+
import com.ninecraft.booket.core.model.RecentBookModel
1214
import com.ninecraft.booket.core.model.RecordRegisterModel
1315
import com.ninecraft.booket.core.model.UserProfileModel
1416
import com.ninecraft.booket.core.network.response.BookDetailResponse
1517
import com.ninecraft.booket.core.network.response.BookSearchResponse
1618
import com.ninecraft.booket.core.network.response.BookSummary
1719
import com.ninecraft.booket.core.network.response.BookUpsertResponse
20+
import com.ninecraft.booket.core.network.response.HomeResponse
1821
import com.ninecraft.booket.core.network.response.LibraryBookSummary
1922
import com.ninecraft.booket.core.network.response.LibraryBooks
2023
import com.ninecraft.booket.core.network.response.LibraryResponse
2124
import com.ninecraft.booket.core.network.response.PageInfo
25+
import com.ninecraft.booket.core.network.response.RecentBook
2226
import com.ninecraft.booket.core.network.response.RecordRegisterResponse
2327
import com.ninecraft.booket.core.network.response.UserProfileResponse
2428

@@ -151,3 +155,21 @@ internal fun RecordRegisterResponse.toModel(): RecordRegisterModel {
151155
updatedAt = updatedAt,
152156
)
153157
}
158+
159+
internal fun HomeResponse.toModel(): HomeModel {
160+
return HomeModel(
161+
recentBooks = recentBooks.map { it.toModel() },
162+
)
163+
}
164+
165+
internal fun RecentBook.toModel(): RecentBookModel {
166+
return RecentBookModel(
167+
userBookId = userBookId,
168+
title = title,
169+
author = author,
170+
publisher = publisher,
171+
coverImageUrl = coverImageUrl,
172+
lastRecordedAt = lastRecordedAt,
173+
recordCount = recordCount,
174+
)
175+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.ninecraft.booket.core.data.impl.repository
2+
3+
import com.ninecraft.booket.core.common.utils.runSuspendCatching
4+
import com.ninecraft.booket.core.data.api.repository.HomeRepository
5+
import com.ninecraft.booket.core.data.impl.mapper.toModel
6+
import com.ninecraft.booket.core.network.service.ReedService
7+
import javax.inject.Inject
8+
9+
class DefaultHomeRepository @Inject constructor(
10+
private val service: ReedService,
11+
) : HomeRepository {
12+
override suspend fun getHome() = runSuspendCatching {
13+
service.getHome().toModel()
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.ninecraft.booket.core.model
2+
3+
import androidx.compose.runtime.Stable
4+
5+
@Stable
6+
data class HomeModel(
7+
val recentBooks: List<RecentBookModel> = emptyList(),
8+
)
9+
10+
@Stable
11+
data class RecentBookModel(
12+
val userBookId: String = "",
13+
val title: String = "",
14+
val author: String = "",
15+
val publisher: String = "",
16+
val coverImageUrl: String = "",
17+
val lastRecordedAt: String = "",
18+
val recordCount: Int = 0,
19+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.ninecraft.booket.core.network.response
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class HomeResponse(
8+
@SerialName("recentBooks")
9+
val recentBooks: List<RecentBook>,
10+
)
11+
12+
@Serializable
13+
data class RecentBook(
14+
@SerialName("userBookId")
15+
val userBookId: String,
16+
@SerialName("title")
17+
val title: String,
18+
@SerialName("author")
19+
val author: String,
20+
@SerialName("publisher")
21+
val publisher: String,
22+
@SerialName("coverImageUrl")
23+
val coverImageUrl: String,
24+
@SerialName("lastRecordedAt")
25+
val lastRecordedAt: String,
26+
@SerialName("recordCount")
27+
val recordCount: Int,
28+
)

core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.ninecraft.booket.core.network.request.TermsAgreementRequest
88
import com.ninecraft.booket.core.network.response.BookDetailResponse
99
import com.ninecraft.booket.core.network.response.BookSearchResponse
1010
import com.ninecraft.booket.core.network.response.BookUpsertResponse
11+
import com.ninecraft.booket.core.network.response.HomeResponse
1112
import com.ninecraft.booket.core.network.response.LibraryResponse
1213
import com.ninecraft.booket.core.network.response.LoginResponse
1314
import com.ninecraft.booket.core.network.response.RecordRegisterResponse
@@ -76,4 +77,10 @@ interface ReedService {
7677
@Path("userBookId") userBookId: String,
7778
@Body recordRegisterRequest: RecordRegisterRequest,
7879
): RecordRegisterResponse
80+
81+
// Home (auth required)
82+
@GET("api/v1/home")
83+
suspend fun getHome(
84+
@Query("limit") limit: Int = 3,
85+
): HomeResponse
7986
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.ninecraft.booket.feature.home
2+
3+
import android.widget.Toast
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.platform.LocalContext
6+
import com.skydoves.compose.effects.RememberedEffect
7+
8+
@Composable
9+
internal fun HandleHomeSideEffects(
10+
state: HomeUiState,
11+
) {
12+
val context = LocalContext.current
13+
14+
RememberedEffect(state.sideEffect) {
15+
when (state.sideEffect) {
16+
is HomeSideEffect.ShowToast -> {
17+
Toast.makeText(context, state.sideEffect.message, Toast.LENGTH_SHORT).show()
18+
}
19+
20+
null -> {}
21+
}
22+
}
23+
}

feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,65 @@
11
package com.ninecraft.booket.feature.home
22

33
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.LaunchedEffect
5+
import androidx.compose.runtime.getValue
6+
import androidx.compose.runtime.mutableStateOf
47
import androidx.compose.runtime.rememberCoroutineScope
8+
import androidx.compose.runtime.setValue
9+
import com.ninecraft.booket.core.common.utils.handleException
10+
import com.ninecraft.booket.core.data.api.repository.HomeRepository
11+
import com.ninecraft.booket.core.model.RecentBookModel
512
import com.ninecraft.booket.feature.screens.BookDetailScreen
613
import com.ninecraft.booket.feature.screens.HomeScreen
14+
import com.ninecraft.booket.feature.screens.LoginScreen
715
import com.ninecraft.booket.feature.screens.RecordScreen
816
import com.ninecraft.booket.feature.screens.SearchScreen
917
import com.ninecraft.booket.feature.screens.SettingsScreen
18+
import com.orhanobut.logger.Logger
1019
import com.slack.circuit.codegen.annotations.CircuitInject
20+
import com.slack.circuit.retained.rememberRetained
1121
import com.slack.circuit.runtime.Navigator
1222
import com.slack.circuit.runtime.presenter.Presenter
1323
import dagger.assisted.Assisted
1424
import dagger.assisted.AssistedFactory
1525
import dagger.assisted.AssistedInject
1626
import dagger.hilt.android.components.ActivityRetainedComponent
27+
import kotlinx.collections.immutable.persistentListOf
28+
import kotlinx.collections.immutable.toPersistentList
29+
import kotlinx.coroutines.launch
1730

18-
@Suppress("unused")
1931
class HomePresenter @AssistedInject constructor(
2032
@Assisted private val navigator: Navigator,
33+
private val repository: HomeRepository,
2134
) : Presenter<HomeUiState> {
2235

2336
@Composable
2437
override fun present(): HomeUiState {
2538
val scope = rememberCoroutineScope()
39+
var sideEffect by rememberRetained { mutableStateOf<HomeSideEffect?>(null) }
40+
var recentBooks by rememberRetained { mutableStateOf(persistentListOf<RecentBookModel>()) }
41+
42+
fun getHome() {
43+
scope.launch {
44+
repository.getHome()
45+
.onSuccess { result ->
46+
recentBooks = result.recentBooks.toPersistentList()
47+
}.onFailure { exception ->
48+
val handleErrorMessage = { message: String ->
49+
Logger.e(message)
50+
sideEffect = HomeSideEffect.ShowToast(message)
51+
}
52+
53+
handleException(
54+
exception = exception,
55+
onError = handleErrorMessage,
56+
onLoginRequired = {
57+
navigator.resetRoot(LoginScreen)
58+
},
59+
)
60+
}
61+
}
62+
}
2663

2764
fun handleEvent(event: HomeUiEvent) {
2865
when (event) {
@@ -35,7 +72,7 @@ class HomePresenter @AssistedInject constructor(
3572
}
3673

3774
is HomeUiEvent.OnRecordButtonClick -> {
38-
navigator.goTo(RecordScreen(""))
75+
navigator.goTo(RecordScreen(event.userBookId))
3976
}
4077

4178
is HomeUiEvent.OnBookDetailClick -> {
@@ -44,7 +81,12 @@ class HomePresenter @AssistedInject constructor(
4481
}
4582
}
4683

84+
LaunchedEffect(true) {
85+
getHome()
86+
}
87+
4788
return HomeUiState(
89+
recentBooks = recentBooks,
4890
eventSink = ::handleEvent,
4991
)
5092
}

feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.ninecraft.booket.core.designsystem.theme.HomeBg
2727
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
2828
import com.ninecraft.booket.core.designsystem.theme.White
2929
import com.ninecraft.booket.feature.home.component.BookCard
30+
import com.ninecraft.booket.feature.home.component.EmptyBookCard
3031
import com.ninecraft.booket.feature.home.component.HomeBanner
3132
import com.ninecraft.booket.feature.home.component.HomeHeader
3233
import com.ninecraft.booket.feature.screens.HomeScreen
@@ -40,6 +41,8 @@ internal fun HomeUi(
4041
state: HomeUiState,
4142
modifier: Modifier = Modifier,
4243
) {
44+
HandleHomeSideEffects(state = state)
45+
4346
Column(
4447
modifier = modifier.fillMaxSize(),
4548
) {
@@ -83,11 +86,6 @@ internal fun HomeContent(
8386
state: HomeUiState,
8487
modifier: Modifier = Modifier,
8588
) {
86-
val dummyBooks = listOf(
87-
Book("여름은 오래 그곳에 남아", "마쓰이에 마사시", "비채", "https://image.aladin.co.kr/product/7492/9/cover200/8934972203_1.jpg", 3),
88-
Book("여름은 오래 그곳에 남아", "마쓰이에 마사시", "비채", "https://image.aladin.co.kr/product/7492/9/cover200/8934972203_1.jpg", 3),
89-
Book("여름은 오래 그곳에 남아", "마쓰이에 마사시", "비채", "https://image.aladin.co.kr/product/7492/9/cover200/8934972203_1.jpg", 3),
90-
)
9189
Column(
9290
modifier = modifier
9391
.fillMaxSize()
@@ -101,38 +99,49 @@ internal fun HomeContent(
10199
style = ReedTheme.typography.headline2Medium,
102100
)
103101
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing3))
104-
val pagerState = rememberPagerState(pageCount = { dummyBooks.size })
105-
HorizontalPager(
106-
state = pagerState,
107-
modifier = Modifier.fillMaxWidth(),
108-
contentPadding = PaddingValues(horizontal = ReedTheme.spacing.spacing5),
109-
pageSpacing = ReedTheme.spacing.spacing5,
110-
) { page ->
111-
BookCard(
112-
bookInfo = dummyBooks[page],
113-
onBookDetailClick = {
114-
state.eventSink(HomeUiEvent.OnBookDetailClick)
115-
},
116-
onRecordButtonClick = {
117-
state.eventSink(HomeUiEvent.OnRecordButtonClick)
102+
103+
if (state.recentBooks.isEmpty()) {
104+
EmptyBookCard(
105+
onBookRegisterClick = {
106+
state.eventSink(HomeUiEvent.OnBookRegisterClick)
118107
},
108+
modifier = Modifier.padding(ReedTheme.spacing.spacing5),
119109
)
120-
}
121-
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing5))
122-
Row(
123-
modifier = Modifier.fillMaxWidth(),
124-
horizontalArrangement = Arrangement.Center,
125-
) {
126-
repeat(pagerState.pageCount) { iteration ->
127-
val color = if (pagerState.currentPage == iteration) ReedTheme.colors.bgPrimary else ReedTheme.colors.bgSecondaryPressed
128-
Box(
129-
modifier = Modifier
130-
.size(12.dp)
131-
.padding(3.dp)
132-
.clip(CircleShape)
133-
.background(color),
110+
} else {
111+
val pagerState = rememberPagerState(pageCount = { state.recentBooks.size })
112+
113+
HorizontalPager(
114+
state = pagerState,
115+
modifier = Modifier.fillMaxWidth(),
116+
contentPadding = PaddingValues(horizontal = ReedTheme.spacing.spacing5),
117+
pageSpacing = ReedTheme.spacing.spacing5,
118+
) { page ->
119+
BookCard(
120+
recentBookInfo = state.recentBooks[page],
121+
onBookDetailClick = {
122+
state.eventSink(HomeUiEvent.OnBookDetailClick)
123+
},
124+
onRecordButtonClick = {
125+
state.eventSink(HomeUiEvent.OnRecordButtonClick(state.recentBooks[page].userBookId))
126+
},
134127
)
135128
}
129+
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing5))
130+
Row(
131+
modifier = Modifier.fillMaxWidth(),
132+
horizontalArrangement = Arrangement.Center,
133+
) {
134+
repeat(pagerState.pageCount) { iteration ->
135+
val color = if (pagerState.currentPage == iteration) ReedTheme.colors.bgPrimary else ReedTheme.colors.bgSecondaryPressed
136+
Box(
137+
modifier = Modifier
138+
.size(12.dp)
139+
.padding(3.dp)
140+
.clip(CircleShape)
141+
.background(color),
142+
)
143+
}
144+
}
136145
}
137146
}
138147
}

0 commit comments

Comments
 (0)