Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.ninecraft.booket.core.common.constants

import androidx.annotation.StringRes

data class ErrorDialogSpec(
val message: String,
val buttonLabelResId: Int,
@StringRes val buttonLabelResId: Int,
val action: () -> Unit,
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface BookRepository {
start: Int,
): Result<BookSearchModel>

suspend fun removeBookRecentSearch(query: String)
suspend fun deleteBookRecentSearch(query: String)

suspend fun getBookDetail(isbn13: String): Result<BookDetailModel>

Expand All @@ -38,9 +38,11 @@ interface BookRepository {
size: Int,
): Result<LibraryModel>

suspend fun removeLibraryRecentSearch(query: String)
suspend fun deleteLibraryRecentSearch(query: String)

suspend fun getHome(): Result<HomeModel>

suspend fun getSeedsStats(userBookId: String): Result<SeedModel>

suspend fun deleteBook(userBookId: String): Result<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ internal class DefaultBookRepository @Inject constructor(
result
}

override suspend fun removeBookRecentSearch(query: String) {
bookRecentSearchDataSource.removeRecentSearch(query)
override suspend fun deleteBookRecentSearch(query: String) {
bookRecentSearchDataSource.deleteRecentSearch(query)
}

override suspend fun getBookDetail(isbn13: String) = runSuspendCatching {
Expand All @@ -53,8 +53,8 @@ internal class DefaultBookRepository @Inject constructor(
result
}

override suspend fun removeLibraryRecentSearch(query: String) {
libraryRecentSearchDataSource.removeRecentSearch(query)
override suspend fun deleteLibraryRecentSearch(query: String) {
libraryRecentSearchDataSource.deleteRecentSearch(query)
}

override suspend fun getHome() = runSuspendCatching {
Expand All @@ -64,4 +64,8 @@ internal class DefaultBookRepository @Inject constructor(
override suspend fun getSeedsStats(userBookId: String) = runSuspendCatching {
service.getSeedsStats(userBookId).toModel()
}

override suspend fun deleteBook(userBookId: String) = runSuspendCatching {
service.deleteBook(userBookId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import kotlinx.coroutines.flow.Flow
interface RecentSearchDataSource {
val recentSearches: Flow<List<String>>
suspend fun addRecentSearch(query: String)
suspend fun removeRecentSearch(query: String)
suspend fun deleteRecentSearch(query: String)
suspend fun clearRecentSearches()
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class DefaultBookRecentSearchDataSource @Inject constructor(
}

@Suppress("TooGenericExceptionCaught")
override suspend fun removeRecentSearch(query: String) {
override suspend fun deleteRecentSearch(query: String) {
dataStore.edit { prefs ->
val currentSearches = prefs[BOOK_RECENT_SEARCHES]?.let { jsonString ->
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class DefaultLibraryRecentSearchDataSource @Inject constructor(
}

@Suppress("TooGenericExceptionCaught")
override suspend fun removeRecentSearch(query: String) {
override suspend fun deleteRecentSearch(query: String) {
dataStore.edit { prefs ->
val currentSearches = prefs[LIBRARY_RECENT_SEARCHES]?.let { jsonString ->
try {
Expand All @@ -76,7 +76,7 @@ class DefaultLibraryRecentSearchDataSource @Inject constructor(
Logger.e(e, "Failed to deserialize recent searches for removal")
mutableListOf()
} catch (e: Exception) {
Logger.e(e, "Unexpected error while removing recent search")
Logger.e(e, "Unexpected error while deleting recent search")
mutableListOf()
}
} ?: mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import okhttp3.Response
import javax.inject.Inject

internal class TokenInterceptor @Inject constructor(
@Suppress("unused")
private val tokenDataSource: TokenDataSource,
) : Interceptor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ interface ReedService {
@Query("sort") sort: String = "CREATED_DATE_DESC",
): LibraryResponse

@DELETE("api/v1/books/my-library/{userBookId}")
suspend fun deleteBook(
@Path("userBookId") userBookId: String,
)

// Reading-records endpoints (auth required)
@POST("api/v1/reading-records/{userBookId}")
suspend fun postRecord(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fun InfinityLazyColumn(
@SuppressLint("ComposableNaming")
@Composable
private fun LazyListState.onLoadMore(
limitCount: Int = 6,
limitCount: Int = LIMIT_COUNT,
loadOnBottom: Boolean = true,
action: () -> Unit,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class BookDetailPresenter @AssistedInject constructor(
var isRecordSortBottomSheetVisible by rememberRetained { mutableStateOf(false) }
var isRecordMenuBottomSheetVisible by rememberRetained { mutableStateOf(false) }
var isRecordDeleteDialogVisible by rememberRetained { mutableStateOf(false) }
var isDetailMenuBottomSheetVisible by rememberRetained { mutableStateOf(false) }
var isBookDeleteDialogVisible by rememberRetained { mutableStateOf(false) }
var sideEffect by rememberRetained { mutableStateOf<BookDetailSideEffect?>(null) }

@Suppress("TooGenericExceptionCaught")
Expand Down Expand Up @@ -212,6 +214,29 @@ class BookDetailPresenter @AssistedInject constructor(
}
}

fun deleteBook(userBookId: String, onSuccess: () -> Unit) {
scope.launch {
bookRepository.deleteBook(userBookId = userBookId)
.onSuccess {
onSuccess()
}
.onFailure { exception ->
val handleErrorMessage = { message: String ->
Logger.e(message)
sideEffect = BookDetailSideEffect.ShowToast(message)
}

handleException(
exception = exception,
onError = handleErrorMessage,
onLoginRequired = {
navigator.resetRoot(LoginScreen)
},
)
}
}
}

LaunchedEffect(Unit) {
initialLoad()
}
Expand Down Expand Up @@ -297,7 +322,7 @@ class BookDetailPresenter @AssistedInject constructor(
isRecordDeleteDialogVisible = true
}

is BookDetailUiEvent.OnDelete -> {
is BookDetailUiEvent.OnDeleteRecord -> {
isRecordDeleteDialogVisible = false
deleteRecord(
readingRecordId = selectedRecordInfo.id,
Expand All @@ -313,6 +338,33 @@ class BookDetailPresenter @AssistedInject constructor(
navigator.goTo(RecordDetailScreen(event.recordId))
}

is BookDetailUiEvent.OnDetailMenuClick -> {
isDetailMenuBottomSheetVisible = true
}

is BookDetailUiEvent.OnDetailMenuBottomSheetDismiss -> {
isDetailMenuBottomSheetVisible = false
}

is BookDetailUiEvent.OnDeleteBookClick -> {
isDetailMenuBottomSheetVisible = false
isBookDeleteDialogVisible = true
}

is BookDetailUiEvent.OnDeleteDialogDismiss -> {
isBookDeleteDialogVisible = false
}

is BookDetailUiEvent.OnDeleteBook -> {
isBookDeleteDialogVisible = false
deleteBook(
userBookId = screen.userBookId,
onSuccess = {
navigator.pop()
},
)
}

is BookDetailUiEvent.OnLoadMore -> {
if (uiState != UiState.Loading && footerState !is FooterState.Loading && !isLastPage) {
loadMoreReadingRecords(startIndex = currentStartIndex + 1)
Expand Down Expand Up @@ -342,6 +394,8 @@ class BookDetailPresenter @AssistedInject constructor(
selectedRecordInfo = selectedRecordInfo,
isRecordMenuBottomSheetVisible = isRecordMenuBottomSheetVisible,
isRecordDeleteDialogVisible = isRecordDeleteDialogVisible,
isDetailMenuBottomSheetVisible = isDetailMenuBottomSheetVisible,
isBookDeleteDialogVisible = isBookDeleteDialogVisible,
sideEffect = sideEffect,
eventSink = ::handleEvent,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ import com.ninecraft.booket.core.model.BookDetailModel
import com.ninecraft.booket.core.ui.ReedScaffold
import com.ninecraft.booket.core.ui.component.InfinityLazyColumn
import com.ninecraft.booket.core.ui.component.LoadStateFooter
import com.ninecraft.booket.core.ui.component.ReedBackTopAppBar
import com.ninecraft.booket.core.ui.component.ReedDialog
import com.ninecraft.booket.core.ui.component.ReedErrorUi
import com.ninecraft.booket.core.ui.component.ReedTopAppBar
import com.ninecraft.booket.feature.detail.R
import com.ninecraft.booket.feature.detail.book.component.BookItem
import com.ninecraft.booket.feature.detail.book.component.BookUpdateBottomSheet
import com.ninecraft.booket.feature.detail.book.component.CollectedSeeds
import com.ninecraft.booket.feature.detail.book.component.DetailMenuBottomSheet
import com.ninecraft.booket.feature.detail.book.component.ReadingRecordsHeader
import com.ninecraft.booket.feature.detail.book.component.RecordItem
import com.ninecraft.booket.feature.detail.book.component.RecordSortBottomSheet
Expand Down Expand Up @@ -152,14 +153,40 @@ internal fun BookDetailUi(
title = stringResource(R.string.record_delete_dialog_title),
confirmButtonText = stringResource(R.string.record_delete_dialog_delete),
onConfirmRequest = {
state.eventSink(BookDetailUiEvent.OnDelete)
state.eventSink(BookDetailUiEvent.OnDeleteRecord)
},
dismissButtonText = stringResource(R.string.record_delete_dialog_cancel),
onDismissRequest = {
state.eventSink(BookDetailUiEvent.OnRecordDeleteDialogDismiss)
},
)
}

if (state.isDetailMenuBottomSheetVisible) {
DetailMenuBottomSheet(
onDismissRequest = {
state.eventSink(BookDetailUiEvent.OnDetailMenuBottomSheetDismiss)
},
sheetState = recordMenuBottomSheetState,
onDeleteBookClick = {
state.eventSink(BookDetailUiEvent.OnDeleteBookClick)
},
)
}

if (state.isBookDeleteDialogVisible) {
ReedDialog(
title = stringResource(R.string.record_delete_dialog_title),
confirmButtonText = stringResource(R.string.record_delete_dialog_delete),
onConfirmRequest = {
state.eventSink(BookDetailUiEvent.OnDeleteBook)
},
dismissButtonText = stringResource(R.string.record_delete_dialog_cancel),
onDismissRequest = {
state.eventSink(BookDetailUiEvent.OnDeleteDialogDismiss)
},
)
}
Comment on lines +177 to +189
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

도서 삭제 다이얼로그 문구 수정 필요: ‘기록’ → ‘도서’

현재 도서 삭제 다이얼로그가 기록 삭제 문자열을 사용하고 있어 사용자에게 혼동을 줄 수 있습니다. strings.xml에 제안한 book_delete_dialog_title을 사용하도록 변경해 주세요. 또한 앞선 코멘트대로 이벤트 명칭을 OnBookDeleteDialogDismiss로 바꿀 경우, 참조도 함께 조정합니다.

적용 diff:

-        ReedDialog(
-            title = stringResource(R.string.record_delete_dialog_title),
-            confirmButtonText = stringResource(R.string.record_delete_dialog_delete),
+        ReedDialog(
+            title = stringResource(R.string.book_delete_dialog_title),
+            confirmButtonText = stringResource(R.string.record_delete_dialog_delete),
             onConfirmRequest = {
                 state.eventSink(BookDetailUiEvent.OnDeleteBook)
             },
             dismissButtonText = stringResource(R.string.record_delete_dialog_cancel),
             onDismissRequest = {
-                state.eventSink(BookDetailUiEvent.OnDeleteDialogDismiss)
+                state.eventSink(BookDetailUiEvent.OnBookDeleteDialogDismiss)
             },
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (state.isBookDeleteDialogVisible) {
ReedDialog(
title = stringResource(R.string.record_delete_dialog_title),
confirmButtonText = stringResource(R.string.record_delete_dialog_delete),
onConfirmRequest = {
state.eventSink(BookDetailUiEvent.OnDeleteBook)
},
dismissButtonText = stringResource(R.string.record_delete_dialog_cancel),
onDismissRequest = {
state.eventSink(BookDetailUiEvent.OnDeleteDialogDismiss)
},
)
}
if (state.isBookDeleteDialogVisible) {
ReedDialog(
title = stringResource(R.string.book_delete_dialog_title),
confirmButtonText = stringResource(R.string.record_delete_dialog_delete),
onConfirmRequest = {
state.eventSink(BookDetailUiEvent.OnDeleteBook)
},
dismissButtonText = stringResource(R.string.record_delete_dialog_cancel),
onDismissRequest = {
state.eventSink(BookDetailUiEvent.OnBookDeleteDialogDismiss)
},
)
}
🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
around lines 177-189, the delete dialog currently uses record-centric strings
and the old dismiss event name; change title and button text to use
stringResource(R.string.book_delete_dialog_title) (and corresponding
book_delete_dialog_delete/book_delete_dialog_cancel if available) instead of the
record_* keys, and update the dismiss handler reference to the renamed event
(BookDetailUiEvent.OnBookDeleteDialogDismiss) so all usages match the new event
name.

}

@Composable
Expand Down Expand Up @@ -191,11 +218,17 @@ internal fun BookDetailContent(
},
) {
item {
ReedBackTopAppBar(
ReedTopAppBar(
title = "",
onBackClick = {
startIconRes = designR.drawable.ic_chevron_left,
startIconOnClick = {
state.eventSink(BookDetailUiEvent.OnBackClick)
},
endIconRes = designR.drawable.ic_more_vertical,
endIconDescription = "More Vertical Icon",
endIconOnClick = {
state.eventSink(BookDetailUiEvent.OnDetailMenuClick)
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID

@Immutable
sealed interface UiState {
data object Idle : UiState
data object Loading : UiState
Expand All @@ -38,6 +39,8 @@ data class BookDetailUiState(
val isRecordSortBottomSheetVisible: Boolean = false,
val isRecordMenuBottomSheetVisible: Boolean = false,
val isRecordDeleteDialogVisible: Boolean = false,
val isDetailMenuBottomSheetVisible: Boolean = false,
val isBookDeleteDialogVisible: Boolean = false,
val sideEffect: BookDetailSideEffect? = null,
val eventSink: (BookDetailUiEvent) -> Unit,
) : CircuitUiState {
Expand Down Expand Up @@ -71,7 +74,12 @@ sealed interface BookDetailUiEvent : CircuitUiEvent {
data object OnRecordDeleteDialogDismiss : BookDetailUiEvent
data object OnEditRecordClick : BookDetailUiEvent
data object OnDeleteRecordClick : BookDetailUiEvent
data object OnDelete : BookDetailUiEvent
data object OnDeleteRecord : BookDetailUiEvent
data object OnDetailMenuClick : BookDetailUiEvent
data object OnDetailMenuBottomSheetDismiss : BookDetailUiEvent
data object OnDeleteBookClick : BookDetailUiEvent
data object OnDeleteDialogDismiss : BookDetailUiEvent
data object OnDeleteBook : BookDetailUiEvent
data class OnRecordItemClick(val recordId: String) : BookDetailUiEvent
data object OnLoadMore : BookDetailUiEvent
data object OnRetryClick : BookDetailUiEvent
Expand Down
Loading
Loading