@@ -3,14 +3,18 @@ package com.ninecraft.booket.feature.detail.book
33import androidx.compose.runtime.Composable
44import androidx.compose.runtime.LaunchedEffect
55import androidx.compose.runtime.getValue
6+ import androidx.compose.runtime.mutableIntStateOf
67import androidx.compose.runtime.mutableStateOf
78import androidx.compose.runtime.rememberCoroutineScope
89import androidx.compose.runtime.setValue
910import com.ninecraft.booket.core.common.constants.BookStatus
1011import com.ninecraft.booket.core.common.utils.handleException
1112import com.ninecraft.booket.core.data.api.repository.BookRepository
13+ import com.ninecraft.booket.core.data.api.repository.RecordRepository
1214import com.ninecraft.booket.core.model.BookDetailModel
1315import com.ninecraft.booket.core.model.EmotionModel
16+ import com.ninecraft.booket.core.model.ReadingRecordModel
17+ import com.ninecraft.booket.core.ui.component.FooterState
1418import com.ninecraft.booket.feature.screens.BookDetailScreen
1519import com.ninecraft.booket.feature.screens.LoginScreen
1620import com.ninecraft.booket.feature.screens.RecordDetailScreen
@@ -27,86 +31,86 @@ import dagger.hilt.android.components.ActivityRetainedComponent
2731import kotlinx.collections.immutable.ImmutableList
2832import kotlinx.collections.immutable.persistentListOf
2933import kotlinx.collections.immutable.toImmutableList
34+ import kotlinx.collections.immutable.toPersistentList
3035import kotlinx.coroutines.launch
3136
3237class BookDetailPresenter @AssistedInject constructor(
3338 @Assisted private val screen : BookDetailScreen ,
3439 @Assisted private val navigator : Navigator ,
35- private val repository : BookRepository ,
40+ private val bookRepository : BookRepository ,
41+ private val recordRepository : RecordRepository ,
3642) : Presenter<BookDetailUiState> {
43+ companion object {
44+ private const val PAGE_SIZE = 20
45+ private const val START_INDEX = 1
46+ }
3747
3848 @Composable
3949 override fun present (): BookDetailUiState {
4050 val scope = rememberCoroutineScope()
41-
42- var isLoading by rememberRetained { mutableStateOf( false ) }
51+ var uiState by rememberRetained { mutableStateOf< UiState >( UiState . Idle ) }
52+ var footerState by rememberRetained { mutableStateOf< FooterState >( FooterState . Idle ) }
4353 var bookDetail by rememberRetained { mutableStateOf(BookDetailModel ()) }
44- var seedsStates by rememberRetained { mutableStateOf<ImmutableList <EmotionModel >>(persistentListOf<EmotionModel >()) }
54+ var seedsStates by rememberRetained { mutableStateOf<ImmutableList <EmotionModel >>(persistentListOf()) }
55+ var readingRecords by rememberRetained { mutableStateOf(persistentListOf<ReadingRecordModel >()) }
56+ var currentStartIndex by rememberRetained { mutableIntStateOf(START_INDEX ) }
57+ var isLastPage by rememberRetained { mutableStateOf(false ) }
58+ var currentBookStatus by rememberRetained { mutableStateOf(BookStatus .READING ) }
59+ var currentRecordSort by rememberRetained { mutableStateOf(RecordSort .PAGE_NUMBER_ASC ) }
4560 var isBookUpdateBottomSheetVisible by rememberRetained { mutableStateOf(false ) }
4661 var isRecordSortBottomSheetVisible by rememberRetained { mutableStateOf(false ) }
47- var currentBookStatus by rememberRetained { mutableStateOf(BookStatus .READING ) }
48- var currentRecordSort by rememberRetained { mutableStateOf(RecordSort .PAGE_ASCENDING ) }
4962 var sideEffect by rememberRetained { mutableStateOf<BookDetailSideEffect ?>(null ) }
5063
5164 fun getSeedsStats () {
5265 scope.launch {
53- try {
54- isLoading = true
55- repository.getSeedsStats(screen.userBookId)
56- .onSuccess { result ->
57- seedsStates = result.categories.toImmutableList()
58- }
59- .onFailure { exception ->
60- val handleErrorMessage = { message: String ->
61- Logger .e(message)
62- sideEffect = BookDetailSideEffect .ShowToast (message)
63- }
64-
65- handleException(
66- exception = exception,
67- onError = handleErrorMessage,
68- onLoginRequired = {
69- navigator.resetRoot(LoginScreen )
70- },
71- )
66+ bookRepository.getSeedsStats(screen.userBookId)
67+ .onSuccess { result ->
68+ seedsStates = result.categories.toImmutableList()
69+ }
70+ .onFailure { exception ->
71+ val handleErrorMessage = { message: String ->
72+ Logger .e(message)
73+ sideEffect = BookDetailSideEffect .ShowToast (message)
7274 }
73- } finally {
74- isLoading = false
75- }
75+
76+ handleException(
77+ exception = exception,
78+ onError = handleErrorMessage,
79+ onLoginRequired = {
80+ navigator.resetRoot(LoginScreen )
81+ },
82+ )
83+ }
7684 }
7785 }
7886
7987 fun getBookDetail () {
8088 scope.launch {
81- try {
82- isLoading = true
83- repository.getBookDetail(screen.isbn)
84- .onSuccess { result ->
85- bookDetail = result
86- }
87- .onFailure { exception ->
88- val handleErrorMessage = { message: String ->
89- Logger .e(message)
90- sideEffect = BookDetailSideEffect .ShowToast (message)
91- }
92-
93- handleException(
94- exception = exception,
95- onError = handleErrorMessage,
96- onLoginRequired = {
97- navigator.resetRoot(LoginScreen )
98- },
99- )
89+ bookRepository.getBookDetail(screen.isbn)
90+ .onSuccess { result ->
91+ bookDetail = result
92+ }
93+ .onFailure { exception ->
94+ val handleErrorMessage = { message: String ->
95+ Logger .e(message)
96+ sideEffect = BookDetailSideEffect .ShowToast (message)
10097 }
101- } finally {
102- isLoading = false
103- }
98+
99+ handleException(
100+ exception = exception,
101+ onError = handleErrorMessage,
102+ onLoginRequired = {
103+ navigator.resetRoot(LoginScreen )
104+ },
105+ )
106+ }
107+
104108 }
105109 }
106110
107111 fun upsertBook (bookIsbn : String , bookStatus : String ) {
108112 scope.launch {
109- repository .upsertBook(bookIsbn, bookStatus)
113+ bookRepository .upsertBook(bookIsbn, bookStatus)
110114 .onSuccess {
111115 isBookUpdateBottomSheetVisible = false
112116 }
@@ -127,9 +131,50 @@ class BookDetailPresenter @AssistedInject constructor(
127131 }
128132 }
129133
134+ fun getReadingRecords (startIndex : Int = START_INDEX ) {
135+ scope.launch {
136+ if (startIndex == START_INDEX ) {
137+ uiState = UiState .Loading
138+ } else {
139+ footerState = FooterState .Loading
140+ }
141+
142+ recordRepository.getReadingRecords(
143+ userBookId = screen.userBookId,
144+ sort = currentRecordSort.value,
145+ page = START_INDEX ,
146+ size = PAGE_SIZE ,
147+ ).onSuccess { result ->
148+ readingRecords = if (startIndex == START_INDEX ) {
149+ result.content.toPersistentList()
150+ } else {
151+ (readingRecords + result.content).toPersistentList()
152+ }
153+
154+ currentStartIndex = startIndex
155+ isLastPage = result.content.size < PAGE_SIZE
156+
157+ if (startIndex == START_INDEX ) {
158+ uiState = UiState .Success
159+ } else {
160+ footerState = if (isLastPage) FooterState .End else FooterState .Idle
161+ }
162+ }.onFailure { exception ->
163+ Logger .d(exception)
164+ val errorMessage = exception.message ? : " 알 수 없는 오류가 발생했습니다."
165+ if (startIndex == START_INDEX ) {
166+ uiState = UiState .Error (errorMessage)
167+ } else {
168+ footerState = FooterState .Error (errorMessage)
169+ }
170+ }
171+ }
172+ }
173+
130174 LaunchedEffect (Unit ) {
131175 getSeedsStats()
132176 getBookDetail()
177+ getReadingRecords()
133178 }
134179
135180 fun handleEvent (event : BookDetailUiEvent ) {
@@ -178,13 +223,22 @@ class BookDetailPresenter @AssistedInject constructor(
178223 is BookDetailUiEvent .OnRecordItemClick -> {
179224 navigator.goTo(RecordDetailScreen (event.recordId))
180225 }
226+
227+ is BookDetailUiEvent .OnLoadMore -> {
228+ if (footerState !is FooterState .Loading && ! isLastPage) {
229+ getReadingRecords(startIndex = currentStartIndex + 1 )
230+ }
231+ }
181232 }
182233 }
183234
184235 return BookDetailUiState (
185- isLoading = isLoading,
236+ uiState = uiState,
237+ footerState = footerState,
186238 bookDetail = bookDetail,
187239 seedsStats = seedsStates,
240+ readingRecords = readingRecords,
241+ currentStartIndex = currentStartIndex,
188242 isBookUpdateBottomSheetVisible = isBookUpdateBottomSheetVisible,
189243 isRecordSortBottomSheetVisible = isRecordSortBottomSheetVisible,
190244 currentBookStatus = currentBookStatus,
0 commit comments