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,9 +1,11 @@
package com.oreocube.booksearch.data.datasource

import com.oreocube.booksearch.data.response.BookNotificationResponse
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget

interface NotificationDataSource {
suspend fun registerNotificationForBookStatus(uid: String, target: BookNotificationTarget)
suspend fun unregisterNotificationForBookStatus(uid: String, target: BookNotificationTarget)
suspend fun isNotificationRegistered(uid: String, libraryId: String, isbn: String): Boolean
suspend fun getAllNotifications(uid: String): List<BookNotificationResponse>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.oreocube.booksearch.data.datasource

import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.QueryDocumentSnapshot
import com.google.firebase.firestore.toObject
import com.oreocube.booksearch.data.response.BookNotificationResponse
import com.oreocube.booksearch.data.response.NotificationSettingDto
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
import kotlinx.coroutines.tasks.await
Expand Down Expand Up @@ -55,6 +58,14 @@ class NotificationDataSourceImpl @Inject constructor(
.exists()
}

override suspend fun getAllNotifications(uid: String): List<BookNotificationResponse> {
return db.collection(NOTIFICATION_COLLECTION)
.whereEqualTo("uid", uid)
.get()
.await()
.map(QueryDocumentSnapshot::toObject)
}

private fun getDocumentId(uid: String, libraryId: String, isbn: String): String {
return "${uid}_${libraryId}_${isbn}"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.oreocube.booksearch.data.repository

import com.oreocube.booksearch.data.datasource.NotificationDataSource
import com.oreocube.booksearch.data.response.BookNotificationResponse
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
import com.oreocube.booksearch.domain.repository.NotificationRepository
import javax.inject.Inject
Expand Down Expand Up @@ -32,4 +33,9 @@ class NotificationRepositoryImpl @Inject constructor(
): Boolean {
return notificationDataSource.isNotificationRegistered(uid, libraryId, isbn)
}

override suspend fun getAllNotifications(uid: String): List<BookNotificationTarget> {
return notificationDataSource.getAllNotifications(uid)
.map(BookNotificationResponse::toModel)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.oreocube.booksearch.data.response

import androidx.annotation.Keep
import com.oreocube.booksearch.domain.model.param.BookNotificationTarget

@Keep
data class BookNotificationResponse(
val libraryId: String = "",
val libraryName: String = "",
val isbn: String = "",
val bookTitle: String = "",
) {
fun toModel() = BookNotificationTarget(
libraryId = libraryId,
libraryName = libraryName,
isbn = isbn,
bookTitle = bookTitle,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ package com.oreocube.booksearch.domain.model
data class LibraryWithAvailability(
val library: LibraryShort,
val availability: Result<BookAvailability>,
val isNotificationRegistered: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ interface NotificationRepository {
suspend fun registerNotificationForBookStatus(uid: String, target: BookNotificationTarget)
suspend fun unregisterNotificationForBookStatus(uid: String, target: BookNotificationTarget)
suspend fun isNotificationRegistered(uid: String, libraryId: String, isbn: String): Boolean
suspend fun getAllNotifications(uid: String): List<BookNotificationTarget>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.oreocube.booksearch.domain.usecase

import com.oreocube.booksearch.domain.model.BookAvailability
import com.oreocube.booksearch.domain.model.LibraryShort
import com.oreocube.booksearch.domain.model.LibraryWithAvailability
import com.oreocube.booksearch.domain.model.param.BookAvailabilityCheckParam
Expand All @@ -12,7 +11,6 @@ import javax.inject.Inject

class CheckBookAvailabilityUseCase @Inject constructor(
private val libraryRepository: LibraryRepository,
private val checkBookNotificationUseCase: CheckBookNotificationUseCase,
) {
suspend operator fun invoke(
isbn: String,
Expand Down Expand Up @@ -40,23 +38,9 @@ class CheckBookAvailabilityUseCase @Inject constructor(
)
}

val isNotificationRegistered =
if (availability.getOrNull().shouldCheckNotificationStatus()) {
runCatching {
checkBookNotificationUseCase(library.id, isbn)
}.getOrDefault(false)
} else {
false
}

return LibraryWithAvailability(
library = library,
availability = availability,
isNotificationRegistered = isNotificationRegistered,
)
}

private fun BookAvailability?.shouldCheckNotificationStatus(): Boolean {
return this?.hasBook == true && !this.loanAvailable
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.oreocube.booksearch.domain.usecase

import com.oreocube.booksearch.domain.model.param.BookNotificationTarget
import com.oreocube.booksearch.domain.repository.NotificationRepository
import com.oreocube.booksearch.domain.repository.UserRepository
import javax.inject.Inject

class GetAllNotificationsUseCase @Inject constructor(
private val userRepository: UserRepository,
private val notificationRepository: NotificationRepository,
) {
suspend operator fun invoke(): List<BookNotificationTarget> {
val uid = userRepository.getCurrentUserId() ?: return emptyList()
return notificationRepository.getAllNotifications(uid)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package com.oreocube.booksearch.feature.book.detail

import com.oreocube.booksearch.domain.model.BookDetail
import com.oreocube.booksearch.domain.model.LibraryShort
import com.oreocube.booksearch.feature.book.model.LibraryBookStatusUiState
import com.oreocube.booksearch.feature.book.model.RecommendedBookUiState

data class BookDetailUiState(
val isFirstEntry: Boolean = true,
val isLoading: Boolean,
val book: BookDetail?,
val status: List<LibraryBookStatusUiState>,
val recommendBooks: List<RecommendedBookUiState> = emptyList(),
) {
companion object {
val initialState = BookDetailUiState(
isFirstEntry = true,
isLoading = true,
book = null,
status = emptyList(),
Expand All @@ -20,6 +23,25 @@ data class BookDetailUiState(
}
}

sealed class BookDetailUiEvent {
data class Error(val message: String) : BookDetailUiEvent()
sealed class BookDetailSideEffect {
data object NavigateToAddLibrary : BookDetailSideEffect()
data class NavigateToBookDetail(val isbn: String) : BookDetailSideEffect()
data class Error(val message: String) : BookDetailSideEffect()
}

sealed class BookDetailIntent {
data object EnterScreen : BookDetailIntent()
data object AddLibraryClick : BookDetailIntent()
data class RefreshBookAvailability(
val library: LibraryShort,
) : BookDetailIntent()

data class ToggleNotification(
val isRegistered: Boolean,
val library: LibraryShort,
) : BookDetailIntent()

data class BookItemClick(
val isbn: String,
) : BookDetailIntent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import coil3.compose.AsyncImage
import com.oreocube.booksearch.core.ui.R
import com.oreocube.booksearch.core.ui.component.LiBookTopBar
Expand All @@ -78,6 +81,8 @@ fun BookDetailRoute(
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

var hasNotificationPermission by remember {
mutableStateOf(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
Expand All @@ -99,12 +104,12 @@ fun BookDetailRoute(
modifier = Modifier.fillMaxSize(),
uiState = uiState,
onBackClick = onBackClick,
onRetryClick = viewModel::refreshBookAvailability,
onRetryClick = { viewModel.submitIntent(BookDetailIntent.RefreshBookAvailability(it)) },
onAlarmClick = { isRegistered, library ->
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|| hasNotificationPermission
) {
viewModel.toggleNotification(isRegistered, library)
viewModel.submitIntent(BookDetailIntent.ToggleNotification(isRegistered, library))
} else {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
Expand All @@ -114,11 +119,14 @@ fun BookDetailRoute(
)

LaunchedEffect(Unit) {
viewModel.eventFlow.collect { event ->
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.submitIntent(BookDetailIntent.EnterScreen)
}
viewModel.sideEffect.collect { event ->
when (event) {
is BookDetailUiEvent.Error -> {
onShowSnackbar(event.message)
}
BookDetailSideEffect.NavigateToAddLibrary -> onAddLibraryClick()
is BookDetailSideEffect.NavigateToBookDetail -> onBookItemClick(event.isbn)
is BookDetailSideEffect.Error -> onShowSnackbar(event.message)
}
}
}
Expand Down
Loading