Skip to content

Commit c5f304d

Browse files
authored
Merge pull request #9 from oreocube/feature/book-detail-refresh
fix: 도서 상세 재진입시 관심 도서관 변경 여부 확인
2 parents effeea6 + 9896826 commit c5f304d

File tree

12 files changed

+192
-63
lines changed

12 files changed

+192
-63
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.oreocube.booksearch.data.datasource
22

3+
import com.oreocube.booksearch.data.response.BookNotificationResponse
34
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
45

56
interface NotificationDataSource {
67
suspend fun registerNotificationForBookStatus(uid: String, target: BookNotificationTarget)
78
suspend fun unregisterNotificationForBookStatus(uid: String, target: BookNotificationTarget)
89
suspend fun isNotificationRegistered(uid: String, libraryId: String, isbn: String): Boolean
10+
suspend fun getAllNotifications(uid: String): List<BookNotificationResponse>
911
}

data/src/main/java/com/oreocube/booksearch/data/datasource/NotificationDataSourceImpl.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.oreocube.booksearch.data.datasource
22

33
import com.google.firebase.firestore.FirebaseFirestore
4+
import com.google.firebase.firestore.QueryDocumentSnapshot
5+
import com.google.firebase.firestore.toObject
6+
import com.oreocube.booksearch.data.response.BookNotificationResponse
47
import com.oreocube.booksearch.data.response.NotificationSettingDto
58
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
69
import kotlinx.coroutines.tasks.await
@@ -55,6 +58,14 @@ class NotificationDataSourceImpl @Inject constructor(
5558
.exists()
5659
}
5760

61+
override suspend fun getAllNotifications(uid: String): List<BookNotificationResponse> {
62+
return db.collection(NOTIFICATION_COLLECTION)
63+
.whereEqualTo("uid", uid)
64+
.get()
65+
.await()
66+
.map(QueryDocumentSnapshot::toObject)
67+
}
68+
5869
private fun getDocumentId(uid: String, libraryId: String, isbn: String): String {
5970
return "${uid}_${libraryId}_${isbn}"
6071
}

data/src/main/java/com/oreocube/booksearch/data/repository/NotificationRepositoryImpl.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.oreocube.booksearch.data.repository
22

33
import com.oreocube.booksearch.data.datasource.NotificationDataSource
4+
import com.oreocube.booksearch.data.response.BookNotificationResponse
45
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
56
import com.oreocube.booksearch.domain.repository.NotificationRepository
67
import javax.inject.Inject
@@ -32,4 +33,9 @@ class NotificationRepositoryImpl @Inject constructor(
3233
): Boolean {
3334
return notificationDataSource.isNotificationRegistered(uid, libraryId, isbn)
3435
}
36+
37+
override suspend fun getAllNotifications(uid: String): List<BookNotificationTarget> {
38+
return notificationDataSource.getAllNotifications(uid)
39+
.map(BookNotificationResponse::toModel)
40+
}
3541
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.oreocube.booksearch.data.response
2+
3+
import androidx.annotation.Keep
4+
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
5+
6+
@Keep
7+
data class BookNotificationResponse(
8+
val libraryId: String = "",
9+
val libraryName: String = "",
10+
val isbn: String = "",
11+
val bookTitle: String = "",
12+
) {
13+
fun toModel() = BookNotificationTarget(
14+
libraryId = libraryId,
15+
libraryName = libraryName,
16+
isbn = isbn,
17+
bookTitle = bookTitle,
18+
)
19+
}

domain/src/main/java/com/oreocube/booksearch/domain/model/LibraryWithAvailability.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ package com.oreocube.booksearch.domain.model
33
data class LibraryWithAvailability(
44
val library: LibraryShort,
55
val availability: Result<BookAvailability>,
6-
val isNotificationRegistered: Boolean = false,
76
)

domain/src/main/java/com/oreocube/booksearch/domain/repository/NotificationRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ interface NotificationRepository {
66
suspend fun registerNotificationForBookStatus(uid: String, target: BookNotificationTarget)
77
suspend fun unregisterNotificationForBookStatus(uid: String, target: BookNotificationTarget)
88
suspend fun isNotificationRegistered(uid: String, libraryId: String, isbn: String): Boolean
9+
suspend fun getAllNotifications(uid: String): List<BookNotificationTarget>
910
}

domain/src/main/java/com/oreocube/booksearch/domain/usecase/CheckBookAvailabilityUseCase.kt

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.oreocube.booksearch.domain.usecase
22

3-
import com.oreocube.booksearch.domain.model.BookAvailability
43
import com.oreocube.booksearch.domain.model.LibraryShort
54
import com.oreocube.booksearch.domain.model.LibraryWithAvailability
65
import com.oreocube.booksearch.domain.model.param.BookAvailabilityCheckParam
@@ -12,7 +11,6 @@ import javax.inject.Inject
1211

1312
class CheckBookAvailabilityUseCase @Inject constructor(
1413
private val libraryRepository: LibraryRepository,
15-
private val checkBookNotificationUseCase: CheckBookNotificationUseCase,
1614
) {
1715
suspend operator fun invoke(
1816
isbn: String,
@@ -40,23 +38,9 @@ class CheckBookAvailabilityUseCase @Inject constructor(
4038
)
4139
}
4240

43-
val isNotificationRegistered =
44-
if (availability.getOrNull().shouldCheckNotificationStatus()) {
45-
runCatching {
46-
checkBookNotificationUseCase(library.id, isbn)
47-
}.getOrDefault(false)
48-
} else {
49-
false
50-
}
51-
5241
return LibraryWithAvailability(
5342
library = library,
5443
availability = availability,
55-
isNotificationRegistered = isNotificationRegistered,
5644
)
5745
}
58-
59-
private fun BookAvailability?.shouldCheckNotificationStatus(): Boolean {
60-
return this?.hasBook == true && !this.loanAvailable
61-
}
6246
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.oreocube.booksearch.domain.usecase
2+
3+
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
4+
import com.oreocube.booksearch.domain.repository.NotificationRepository
5+
import com.oreocube.booksearch.domain.repository.UserRepository
6+
import javax.inject.Inject
7+
8+
class GetAllNotificationsUseCase @Inject constructor(
9+
private val userRepository: UserRepository,
10+
private val notificationRepository: NotificationRepository,
11+
) {
12+
suspend operator fun invoke(): List<BookNotificationTarget> {
13+
val uid = userRepository.getCurrentUserId() ?: return emptyList()
14+
return notificationRepository.getAllNotifications(uid)
15+
}
16+
}
Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package com.oreocube.booksearch.feature.book.detail
22

33
import com.oreocube.booksearch.domain.model.BookDetail
4+
import com.oreocube.booksearch.domain.model.LibraryShort
45
import com.oreocube.booksearch.feature.book.model.LibraryBookStatusUiState
56
import com.oreocube.booksearch.feature.book.model.RecommendedBookUiState
67

78
data class BookDetailUiState(
9+
val isFirstEntry: Boolean = true,
810
val isLoading: Boolean,
911
val book: BookDetail?,
1012
val status: List<LibraryBookStatusUiState>,
1113
val recommendBooks: List<RecommendedBookUiState> = emptyList(),
1214
) {
1315
companion object {
1416
val initialState = BookDetailUiState(
17+
isFirstEntry = true,
1518
isLoading = true,
1619
book = null,
1720
status = emptyList(),
@@ -20,6 +23,25 @@ data class BookDetailUiState(
2023
}
2124
}
2225

23-
sealed class BookDetailUiEvent {
24-
data class Error(val message: String) : BookDetailUiEvent()
26+
sealed class BookDetailSideEffect {
27+
data object NavigateToAddLibrary : BookDetailSideEffect()
28+
data class NavigateToBookDetail(val isbn: String) : BookDetailSideEffect()
29+
data class Error(val message: String) : BookDetailSideEffect()
30+
}
31+
32+
sealed class BookDetailIntent {
33+
data object EnterScreen : BookDetailIntent()
34+
data object AddLibraryClick : BookDetailIntent()
35+
data class RefreshBookAvailability(
36+
val library: LibraryShort,
37+
) : BookDetailIntent()
38+
39+
data class ToggleNotification(
40+
val isRegistered: Boolean,
41+
val library: LibraryShort,
42+
) : BookDetailIntent()
43+
44+
data class BookItemClick(
45+
val isbn: String,
46+
) : BookDetailIntent()
2547
}

feature/book/src/main/java/com/oreocube/booksearch/feature/book/detail/BookDetailScreen.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ import androidx.compose.ui.unit.dp
5454
import androidx.compose.ui.unit.sp
5555
import androidx.core.content.ContextCompat
5656
import androidx.hilt.navigation.compose.hiltViewModel
57+
import androidx.lifecycle.Lifecycle
58+
import androidx.lifecycle.compose.LocalLifecycleOwner
5759
import androidx.lifecycle.compose.collectAsStateWithLifecycle
60+
import androidx.lifecycle.repeatOnLifecycle
5861
import coil3.compose.AsyncImage
5962
import com.oreocube.booksearch.core.ui.R
6063
import com.oreocube.booksearch.core.ui.component.LiBookTopBar
@@ -78,6 +81,8 @@ fun BookDetailRoute(
7881
) {
7982
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
8083
val context = LocalContext.current
84+
val lifecycleOwner = LocalLifecycleOwner.current
85+
8186
var hasNotificationPermission by remember {
8287
mutableStateOf(
8388
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
@@ -99,12 +104,12 @@ fun BookDetailRoute(
99104
modifier = Modifier.fillMaxSize(),
100105
uiState = uiState,
101106
onBackClick = onBackClick,
102-
onRetryClick = viewModel::refreshBookAvailability,
107+
onRetryClick = { viewModel.submitIntent(BookDetailIntent.RefreshBookAvailability(it)) },
103108
onAlarmClick = { isRegistered, library ->
104109
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
105110
|| hasNotificationPermission
106111
) {
107-
viewModel.toggleNotification(isRegistered, library)
112+
viewModel.submitIntent(BookDetailIntent.ToggleNotification(isRegistered, library))
108113
} else {
109114
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
110115
}
@@ -114,11 +119,14 @@ fun BookDetailRoute(
114119
)
115120

116121
LaunchedEffect(Unit) {
117-
viewModel.eventFlow.collect { event ->
122+
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
123+
viewModel.submitIntent(BookDetailIntent.EnterScreen)
124+
}
125+
viewModel.sideEffect.collect { event ->
118126
when (event) {
119-
is BookDetailUiEvent.Error -> {
120-
onShowSnackbar(event.message)
121-
}
127+
BookDetailSideEffect.NavigateToAddLibrary -> onAddLibraryClick()
128+
is BookDetailSideEffect.NavigateToBookDetail -> onBookItemClick(event.isbn)
129+
is BookDetailSideEffect.Error -> onShowSnackbar(event.message)
122130
}
123131
}
124132
}

0 commit comments

Comments
 (0)