refactor: bookInfo viewmodel
This commit is contained in:
parent
28cbf1188d
commit
6f53c70b5b
23 changed files with 276 additions and 224 deletions
|
|
@ -27,7 +27,7 @@ class CanResetCoverImageUseCase @Inject constructor(
|
||||||
// Getting default cover image
|
// Getting default cover image
|
||||||
val defaultCoverImage = bookRepository.getDefaultCover(book).getOrThrow()
|
val defaultCoverImage = bookRepository.getDefaultCover(book).getOrThrow()
|
||||||
if (defaultCoverImage == null) {
|
if (defaultCoverImage == null) {
|
||||||
return@mapCatching true
|
return@mapCatching false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if current cover is null (and default is not)
|
// Return true if current cover is null (and default is not)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class UpdateCoverImageUseCase @Inject constructor(
|
||||||
logI("Updating cover image of [$bookId].")
|
logI("Updating cover image of [$bookId].")
|
||||||
|
|
||||||
bookRepository.getBook(bookId).mapCatching { book ->
|
bookRepository.getBook(bookId).mapCatching { book ->
|
||||||
|
if (book.coverImage == coverImage) return
|
||||||
|
|
||||||
// Deleting old cover
|
// Deleting old cover
|
||||||
book.coverImage?.let { coverImageHandler.deleteCover(it) }?.onFailure {
|
book.coverImage?.let { coverImageHandler.deleteCover(it) }?.onFailure {
|
||||||
logW("Could not delete old cover image with error: ${it.message}")
|
logW("Could not delete old cover image with error: ${it.message}")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Book's Story — free and open-source Material You eBook reader.
|
||||||
|
* Copyright (C) 2024-2025 Acclorite
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ua.acclorite.book_story.presentation.book_info
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed class BookInfoEffect {
|
||||||
|
data object OnChangedCover : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnErrorResetCover : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnResetCover : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnDeletedCover : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnTitleChanged : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnAuthorChanged : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnDescriptionChanged : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnPathChanged : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnBookDeleted : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnBookMoved : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnNavigateBack : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnNavigateToLibrarySettings : BookInfoEffect()
|
||||||
|
|
||||||
|
data object OnNavigateToReader : BookInfoEffect()
|
||||||
|
}
|
||||||
|
|
@ -6,31 +6,24 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.presentation.book_info
|
package ua.acclorite.book_story.presentation.book_info
|
||||||
|
|
||||||
import android.content.Context
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.core.ui.UIText
|
import ua.acclorite.book_story.core.ui.UIText
|
||||||
import ua.acclorite.book_story.domain.model.library.Category
|
import ua.acclorite.book_story.domain.model.library.Category
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class BookInfoEvent {
|
sealed class BookInfoEvent {
|
||||||
|
|
||||||
data object OnShowDetailsBottomSheet : BookInfoEvent()
|
data object OnShowDetailsBottomSheet : BookInfoEvent()
|
||||||
|
|
||||||
data object OnShowChangeCoverBottomSheet : BookInfoEvent()
|
data object OnShowChangeCoverBottomSheet : BookInfoEvent()
|
||||||
|
|
||||||
data class OnChangeCover(
|
data class OnChangeCover(
|
||||||
val uri: Uri,
|
val image: Bitmap
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data class OnResetCover(
|
data object OnResetCover : BookInfoEvent()
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
|
||||||
|
|
||||||
data class OnDeleteCover(
|
data object OnDeleteCover : BookInfoEvent()
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
|
||||||
|
|
||||||
data object OnCheckCoverReset : BookInfoEvent()
|
data object OnCheckCoverReset : BookInfoEvent()
|
||||||
|
|
||||||
|
|
@ -39,44 +32,42 @@ sealed class BookInfoEvent {
|
||||||
data object OnShowTitleDialog : BookInfoEvent()
|
data object OnShowTitleDialog : BookInfoEvent()
|
||||||
|
|
||||||
data class OnActionTitleDialog(
|
data class OnActionTitleDialog(
|
||||||
val title: String,
|
val title: String
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data object OnShowAuthorDialog : BookInfoEvent()
|
data object OnShowAuthorDialog : BookInfoEvent()
|
||||||
|
|
||||||
data class OnActionAuthorDialog(
|
data class OnActionAuthorDialog(
|
||||||
val author: UIText,
|
val author: UIText
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data object OnShowDescriptionDialog : BookInfoEvent()
|
data object OnShowDescriptionDialog : BookInfoEvent()
|
||||||
|
|
||||||
data class OnActionDescriptionDialog(
|
data class OnActionDescriptionDialog(
|
||||||
val description: String?,
|
val description: String?
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data object OnShowPathDialog : BookInfoEvent()
|
data object OnShowPathDialog : BookInfoEvent()
|
||||||
|
|
||||||
data class OnActionPathDialog(
|
data class OnActionPathDialog(
|
||||||
val path: String,
|
val path: String
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data object OnShowDeleteDialog : BookInfoEvent()
|
data object OnShowDeleteDialog : BookInfoEvent()
|
||||||
|
|
||||||
data class OnActionDeleteDialog(
|
data object OnActionDeleteDialog : BookInfoEvent()
|
||||||
val context: Context,
|
|
||||||
val navigateBack: () -> Unit
|
|
||||||
) : BookInfoEvent()
|
|
||||||
|
|
||||||
data object OnShowMoveDialog : BookInfoEvent()
|
data object OnShowMoveDialog : BookInfoEvent()
|
||||||
|
|
||||||
data class OnActionMoveDialog(
|
data class OnActionMoveDialog(
|
||||||
val selectedCategories: List<Category>,
|
val selectedCategories: List<Category>
|
||||||
val context: Context
|
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data object OnDismissDialog : BookInfoEvent()
|
data object OnDismissDialog : BookInfoEvent()
|
||||||
|
|
||||||
|
data object OnNavigateBack : BookInfoEvent()
|
||||||
|
|
||||||
|
data object OnNavigateToLibrarySettings : BookInfoEvent()
|
||||||
|
|
||||||
|
data object OnNavigateToReader : BookInfoEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -6,21 +6,20 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.presentation.book_info
|
package ua.acclorite.book_story.presentation.book_info
|
||||||
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.yield
|
|
||||||
import ua.acclorite.book_story.R
|
|
||||||
import ua.acclorite.book_story.domain.use_case.book.CanResetCoverImageUseCase
|
import ua.acclorite.book_story.domain.use_case.book.CanResetCoverImageUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase
|
import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase
|
import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase
|
||||||
|
|
@ -31,8 +30,8 @@ import ua.acclorite.book_story.domain.use_case.book.UpdateCoverImageUseCase
|
||||||
import ua.acclorite.book_story.presentation.browse.BrowseScreen
|
import ua.acclorite.book_story.presentation.browse.BrowseScreen
|
||||||
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
||||||
import ua.acclorite.book_story.presentation.library.LibraryScreen
|
import ua.acclorite.book_story.presentation.library.LibraryScreen
|
||||||
import ua.acclorite.book_story.ui.common.helpers.showToast
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class BookInfoModel @Inject constructor(
|
class BookInfoModel @Inject constructor(
|
||||||
|
|
@ -50,11 +49,13 @@ class BookInfoModel @Inject constructor(
|
||||||
private val _state = MutableStateFlow(BookInfoState())
|
private val _state = MutableStateFlow(BookInfoState())
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
private var eventJob = SupervisorJob()
|
private val _effects = MutableSharedFlow<BookInfoEffect>()
|
||||||
private var resetJob: Job? = null
|
val effects = _effects.asSharedFlow()
|
||||||
|
|
||||||
|
private val eventStack = mutableListOf<Job>()
|
||||||
|
|
||||||
fun onEvent(event: BookInfoEvent) {
|
fun onEvent(event: BookInfoEvent) {
|
||||||
viewModelScope.launch(eventJob + Dispatchers.Main) {
|
viewModelScope.launch {
|
||||||
when (event) {
|
when (event) {
|
||||||
is BookInfoEvent.OnShowDetailsBottomSheet -> {
|
is BookInfoEvent.OnShowDetailsBottomSheet -> {
|
||||||
_state.update {
|
_state.update {
|
||||||
|
|
@ -73,20 +74,15 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnChangeCover -> {
|
is BookInfoEvent.OnChangeCover -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
val image = event.context.contentResolver?.openInputStream(event.uri)?.use {
|
updateCoverImageUseCase(_state.value.book.id, event.image)
|
||||||
BitmapFactory.decodeStream(it)
|
val updatedCoverImage = getBookUseCase(_state.value.book.id)?.coverImage
|
||||||
} ?: return@launch
|
?: return@withContext
|
||||||
|
|
||||||
updateCoverImageUseCase(_state.value.book.id, image)
|
|
||||||
|
|
||||||
val newCoverImage = getBookUseCase(_state.value.book.id)?.coverImage
|
|
||||||
?: return@launch
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
coverImage = newCoverImage
|
coverImage = updatedCoverImage
|
||||||
),
|
),
|
||||||
bottomSheet = null,
|
bottomSheet = null,
|
||||||
canResetCover = canResetCoverImageUseCase(it.book.id)
|
canResetCover = canResetCoverImageUseCase(it.book.id)
|
||||||
|
|
@ -96,33 +92,22 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnChangedCover)
|
||||||
event.context.getString(R.string.cover_image_changed)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnResetCover -> {
|
is BookInfoEvent.OnResetCover -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
val result = resetCoverImageUseCase(_state.value.book.id)
|
val result = resetCoverImageUseCase(_state.value.book.id)
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnErrorResetCover)
|
||||||
event.context.getString(R.string.error_could_not_reset_cover)
|
return@withContext
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val book = getBookUseCase(_state.value.book.id)
|
val book = getBookUseCase(_state.value.book.id)
|
||||||
|
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnErrorResetCover)
|
||||||
event.context.getString(R.string.error_something_went_wrong)
|
return@withContext
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
|
|
@ -133,22 +118,15 @@ class BookInfoModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
event.context.getString(R.string.cover_reset)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
|
_effects.emit(BookInfoEffect.OnResetCover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnDeleteCover -> {
|
is BookInfoEvent.OnDeleteCover -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
if (_state.value.book.coverImage == null) {
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCoverImageUseCase(_state.value.book.id, null)
|
updateCoverImageUseCase(_state.value.book.id, null)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -163,25 +141,21 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnDeletedCover)
|
||||||
event.context.getString(R.string.cover_image_deleted)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnCheckCoverReset -> {
|
is BookInfoEvent.OnCheckCoverReset -> {
|
||||||
launch(Dispatchers.IO) {
|
withContext(Dispatchers.Default) {
|
||||||
if (_state.value.book.id == -1) return@launch
|
if (_state.value.book.id == -1) return@withContext
|
||||||
canResetCoverImageUseCase(_state.value.book.id).apply {
|
val canResetCover = canResetCoverImageUseCase(_state.value.book.id)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
canResetCover = this
|
canResetCover = canResetCover
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is BookInfoEvent.OnDismissBottomSheet -> {
|
is BookInfoEvent.OnDismissBottomSheet -> {
|
||||||
_state.update {
|
_state.update {
|
||||||
|
|
@ -200,7 +174,7 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnActionTitleDialog -> {
|
is BookInfoEvent.OnActionTitleDialog -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -213,10 +187,7 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnTitleChanged)
|
||||||
event.context.getString(R.string.title_changed)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +200,7 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnActionAuthorDialog -> {
|
is BookInfoEvent.OnActionAuthorDialog -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -242,10 +213,7 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnAuthorChanged)
|
||||||
event.context.getString(R.string.author_changed)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,7 +226,7 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnActionDescriptionDialog -> {
|
is BookInfoEvent.OnActionDescriptionDialog -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -271,10 +239,7 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnDescriptionChanged)
|
||||||
event.context.getString(R.string.description_changed)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,7 +252,7 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnActionPathDialog -> {
|
is BookInfoEvent.OnActionPathDialog -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -300,10 +265,7 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnPathChanged)
|
||||||
event.context.getString(R.string.path_changed)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
val file = getFileFromBookUseCase(_state.value.book.id)
|
val file = getFileFromBookUseCase(_state.value.book.id)
|
||||||
_state.update {
|
_state.update {
|
||||||
|
|
@ -323,26 +285,21 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnActionDeleteDialog -> {
|
is BookInfoEvent.OnActionDeleteDialog -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialog = null,
|
dialog = null,
|
||||||
bottomSheet = null
|
bottomSheet = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteBookUseCase(_state.value.book)
|
deleteBookUseCase(_state.value.book)
|
||||||
|
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
BrowseScreen.refreshListChannel.trySend(Unit)
|
BrowseScreen.refreshListChannel.trySend(Unit)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnBookDeleted)
|
||||||
event.context.getString(R.string.book_deleted)
|
_effects.emit(BookInfoEffect.OnNavigateBack)
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
event.navigateBack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,7 +312,7 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnActionMoveDialog -> {
|
is BookInfoEvent.OnActionMoveDialog -> {
|
||||||
launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -370,10 +327,7 @@ class BookInfoModel @Inject constructor(
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
HistoryScreen.refreshListChannel.trySend(0)
|
HistoryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(BookInfoEffect.OnBookMoved)
|
||||||
event.context.getString(R.string.book_moved)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,28 +338,35 @@ class BookInfoModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is BookInfoEvent.OnNavigateBack -> {
|
||||||
|
_effects.emit(BookInfoEffect.OnNavigateBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEvent.OnNavigateToLibrarySettings -> {
|
||||||
|
_effects.emit(BookInfoEffect.OnNavigateToLibrarySettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEvent.OnNavigateToReader -> {
|
||||||
|
_effects.emit(BookInfoEffect.OnNavigateToReader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.also { eventStack.add(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(
|
fun init(
|
||||||
bookId: Int,
|
bookId: Int,
|
||||||
changePath: Boolean,
|
changePath: Boolean
|
||||||
navigateBack: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
val book = getBookUseCase(bookId)
|
val book = getBookUseCase(bookId)
|
||||||
|
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
navigateBack()
|
_effects.emit(BookInfoEffect.OnNavigateBack)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
eventJob.cancel()
|
clear()
|
||||||
resetJob?.cancel()
|
|
||||||
eventJob.join()
|
|
||||||
resetJob?.join()
|
|
||||||
eventJob = SupervisorJob()
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
BookInfoState(
|
BookInfoState(
|
||||||
|
|
@ -413,9 +374,7 @@ class BookInfoModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changePath) {
|
if (changePath) onEvent(BookInfoEvent.OnShowPathDialog)
|
||||||
onEvent(BookInfoEvent.OnShowPathDialog)
|
|
||||||
}
|
|
||||||
onEvent(BookInfoEvent.OnCheckCoverReset)
|
onEvent(BookInfoEvent.OnCheckCoverReset)
|
||||||
|
|
||||||
val file = getFileFromBookUseCase(bookId)
|
val file = getFileFromBookUseCase(bookId)
|
||||||
|
|
@ -427,19 +386,26 @@ class BookInfoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetScreen() {
|
fun clearAsync() {
|
||||||
resetJob = viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch {
|
||||||
eventJob.cancel()
|
eventStack.forEach { job ->
|
||||||
eventJob = SupervisorJob()
|
job.cancel()
|
||||||
|
}
|
||||||
yield()
|
|
||||||
_state.update { BookInfoState() }
|
_state.update { BookInfoState() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun clear() {
|
||||||
|
eventStack.forEach { job ->
|
||||||
|
job.cancel()
|
||||||
|
job.join()
|
||||||
|
}
|
||||||
|
_state.update { BookInfoState() }
|
||||||
|
}
|
||||||
|
|
||||||
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
|
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
yield()
|
coroutineContext.ensureActive()
|
||||||
this.value = function(this.value)
|
this.value = function(this.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.channels.getOrElse
|
import kotlinx.coroutines.channels.getOrElse
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
|
||||||
import ua.acclorite.book_story.presentation.navigator.Screen
|
import ua.acclorite.book_story.presentation.navigator.Screen
|
||||||
import ua.acclorite.book_story.presentation.reader.ReaderScreen
|
|
||||||
import ua.acclorite.book_story.presentation.settings.LibrarySettingsScreen
|
|
||||||
import ua.acclorite.book_story.presentation.settings.SettingsModel
|
import ua.acclorite.book_story.presentation.settings.SettingsModel
|
||||||
import ua.acclorite.book_story.ui.book_info.BookInfoContent
|
import ua.acclorite.book_story.ui.book_info.BookInfoContent
|
||||||
import ua.acclorite.book_story.ui.navigator.LocalNavigator
|
import ua.acclorite.book_story.ui.book_info.BookInfoEffects
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
|
data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
|
||||||
|
|
@ -46,7 +43,6 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.current
|
|
||||||
val screenModel = hiltViewModel<BookInfoModel>()
|
val screenModel = hiltViewModel<BookInfoModel>()
|
||||||
val settingsModel = hiltViewModel<SettingsModel>()
|
val settingsModel = hiltViewModel<SettingsModel>()
|
||||||
|
|
||||||
|
|
@ -57,19 +53,21 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.init(
|
screenModel.init(
|
||||||
bookId = bookId,
|
bookId = bookId,
|
||||||
changePath = changePathChannel.tryReceive().getOrElse { false },
|
changePath = changePathChannel.tryReceive().getOrElse { false }
|
||||||
navigateBack = {
|
|
||||||
navigator.pop()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
screenModel.resetScreen()
|
screenModel.clearAsync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BookInfoEffects(
|
||||||
|
effects = screenModel.effects,
|
||||||
|
book = state.value.book
|
||||||
|
)
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize())
|
Box(Modifier.fillMaxSize())
|
||||||
|
|
||||||
if (state.value.book.id == bookId) {
|
if (state.value.book.id == bookId) {
|
||||||
|
|
@ -101,18 +99,9 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
|
||||||
actionMoveDialog = screenModel::onEvent,
|
actionMoveDialog = screenModel::onEvent,
|
||||||
showDeleteDialog = screenModel::onEvent,
|
showDeleteDialog = screenModel::onEvent,
|
||||||
actionDeleteDialog = screenModel::onEvent,
|
actionDeleteDialog = screenModel::onEvent,
|
||||||
navigateToReader = {
|
navigateToReader = screenModel::onEvent,
|
||||||
if (state.value.book.id != -1) {
|
navigateToLibrarySettings = screenModel::onEvent,
|
||||||
HistoryScreen.insertHistoryChannel.trySend(state.value.book.id)
|
navigateBack = screenModel::onEvent
|
||||||
navigator.push(ReaderScreen(state.value.book.id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navigateToLibrarySettings = {
|
|
||||||
navigator.push(LibrarySettingsScreen)
|
|
||||||
},
|
|
||||||
navigateBack = {
|
|
||||||
navigator.pop()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ fun AboutEffects(effects: SharedFlow<AboutEffect>) {
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val activity = LocalActivity.current
|
val activity = LocalActivity.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(effects, activity, navigator) {
|
||||||
effects.collect { effect ->
|
effects.collect { effect ->
|
||||||
when (effect) {
|
when (effect) {
|
||||||
is AboutEffect.OnNavigateToBrowserPage -> {
|
is AboutEffect.OnNavigateToBrowserPage -> {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
package ua.acclorite.book_story.ui.book_info
|
package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.core.ui.UIText
|
import ua.acclorite.book_story.core.ui.UIText
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
|
|
@ -20,7 +19,6 @@ fun BookInfoAuthorDialog(
|
||||||
actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit,
|
actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
DialogWithTextField(
|
DialogWithTextField(
|
||||||
initialValue = book.author.getAsString() ?: "",
|
initialValue = book.author.getAsString() ?: "",
|
||||||
lengthLimit = 100,
|
lengthLimit = 100,
|
||||||
|
|
@ -31,8 +29,7 @@ fun BookInfoAuthorDialog(
|
||||||
actionAuthorDialog(
|
actionAuthorDialog(
|
||||||
BookInfoEvent.OnActionAuthorDialog(
|
BookInfoEvent.OnActionAuthorDialog(
|
||||||
author = if (it.isBlank()) UIText.StringResource(R.string.unknown_author)
|
author = if (it.isBlank()) UIText.StringResource(R.string.unknown_author)
|
||||||
else UIText.StringValue(it.trim().replace("\n", "")),
|
else UIText.StringValue(it.trim().replace("\n", ""))
|
||||||
context = context
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoBackHandler(
|
fun BookInfoBackHandler(
|
||||||
navigateBack: () -> Unit
|
navigateBack: (BookInfoEvent.OnNavigateBack) -> Unit
|
||||||
) {
|
) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
navigateBack()
|
navigateBack(BookInfoEvent.OnNavigateBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.ui.book_info
|
package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
|
@ -43,10 +44,13 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
contract = ActivityResultContracts.PickVisualMedia(),
|
contract = ActivityResultContracts.PickVisualMedia(),
|
||||||
onResult = { uri ->
|
onResult = { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
val image = context.contentResolver?.openInputStream(uri)?.use {
|
||||||
|
BitmapFactory.decodeStream(it)
|
||||||
|
} ?: return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
changeCover(
|
changeCover(
|
||||||
BookInfoEvent.OnChangeCover(
|
BookInfoEvent.OnChangeCover(
|
||||||
uri = uri,
|
image = image
|
||||||
context = context
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -72,11 +76,7 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
text = stringResource(id = R.string.reset_cover),
|
text = stringResource(id = R.string.reset_cover),
|
||||||
description = stringResource(id = R.string.reset_cover_desc)
|
description = stringResource(id = R.string.reset_cover_desc)
|
||||||
) {
|
) {
|
||||||
resetCover(
|
resetCover(BookInfoEvent.OnResetCover)
|
||||||
BookInfoEvent.OnResetCover(
|
|
||||||
context = context
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,11 +100,7 @@ fun BookInfoChangeCoverBottomSheet(
|
||||||
text = stringResource(id = R.string.delete_cover),
|
text = stringResource(id = R.string.delete_cover),
|
||||||
description = stringResource(id = R.string.delete_cover_desc)
|
description = stringResource(id = R.string.delete_cover_desc)
|
||||||
) {
|
) {
|
||||||
deleteCover(
|
deleteCover(BookInfoEvent.OnDeleteCover)
|
||||||
BookInfoEvent.OnDeleteCover(
|
|
||||||
context = context
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ fun BookInfoContent(
|
||||||
checkCoverReset: (BookInfoEvent.OnCheckCoverReset) -> Unit,
|
checkCoverReset: (BookInfoEvent.OnCheckCoverReset) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
||||||
dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit,
|
dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit,
|
||||||
navigateToReader: () -> Unit,
|
navigateToReader: (BookInfoEvent.OnNavigateToReader) -> Unit,
|
||||||
navigateToLibrarySettings: () -> Unit,
|
navigateToLibrarySettings: (BookInfoEvent.OnNavigateToLibrarySettings) -> Unit,
|
||||||
navigateBack: () -> Unit
|
navigateBack: (BookInfoEvent.OnNavigateBack) -> Unit
|
||||||
) {
|
) {
|
||||||
BookInfoDialog(
|
BookInfoDialog(
|
||||||
dialog = dialog,
|
dialog = dialog,
|
||||||
|
|
@ -59,7 +59,6 @@ fun BookInfoContent(
|
||||||
actionDeleteDialog = actionDeleteDialog,
|
actionDeleteDialog = actionDeleteDialog,
|
||||||
actionMoveDialog = actionMoveDialog,
|
actionMoveDialog = actionMoveDialog,
|
||||||
dismissDialog = dismissDialog,
|
dismissDialog = dismissDialog,
|
||||||
navigateBack = navigateBack,
|
|
||||||
navigateToLibrarySettings = navigateToLibrarySettings
|
navigateToLibrarySettings = navigateToLibrarySettings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package ua.acclorite.book_story.ui.book_info
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
|
|
@ -18,11 +17,8 @@ import ua.acclorite.book_story.ui.common.components.dialog.Dialog
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoDeleteDialog(
|
fun BookInfoDeleteDialog(
|
||||||
actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit,
|
actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
||||||
navigateBack: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
title = stringResource(id = R.string.delete_book),
|
title = stringResource(id = R.string.delete_book),
|
||||||
icon = Icons.Outlined.DeleteOutline,
|
icon = Icons.Outlined.DeleteOutline,
|
||||||
|
|
@ -33,12 +29,7 @@ fun BookInfoDeleteDialog(
|
||||||
withContent = false,
|
withContent = false,
|
||||||
actionEnabled = true,
|
actionEnabled = true,
|
||||||
onAction = {
|
onAction = {
|
||||||
actionDeleteDialog(
|
actionDeleteDialog(BookInfoEvent.OnActionDeleteDialog)
|
||||||
BookInfoEvent.OnActionDeleteDialog(
|
|
||||||
context = context,
|
|
||||||
navigateBack = navigateBack
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
package ua.acclorite.book_story.ui.book_info
|
package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
import ua.acclorite.book_story.ui.common.components.dialog.DialogWithTextField
|
import ua.acclorite.book_story.ui.common.components.dialog.DialogWithTextField
|
||||||
|
|
@ -18,7 +17,6 @@ fun BookInfoDescriptionDialog(
|
||||||
actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit,
|
actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
DialogWithTextField(
|
DialogWithTextField(
|
||||||
initialValue = book.description ?: "",
|
initialValue = book.description ?: "",
|
||||||
lengthLimit = 5000,
|
lengthLimit = 5000,
|
||||||
|
|
@ -29,8 +27,7 @@ fun BookInfoDescriptionDialog(
|
||||||
actionDescriptionDialog(
|
actionDescriptionDialog(
|
||||||
BookInfoEvent.OnActionDescriptionDialog(
|
BookInfoEvent.OnActionDescriptionDialog(
|
||||||
description = if (it.isBlank()) null
|
description = if (it.isBlank()) null
|
||||||
else it.trim().replace("\n", ""),
|
else it.trim().replace("\n", "")
|
||||||
context = context
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,13 @@ fun BookInfoDialog(
|
||||||
actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit,
|
actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit,
|
||||||
actionMoveDialog: (BookInfoEvent.OnActionMoveDialog) -> Unit,
|
actionMoveDialog: (BookInfoEvent.OnActionMoveDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
||||||
navigateBack: () -> Unit,
|
navigateToLibrarySettings: (BookInfoEvent.OnNavigateToLibrarySettings) -> Unit
|
||||||
navigateToLibrarySettings: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
when (dialog) {
|
when (dialog) {
|
||||||
BookInfoScreen.DELETE_DIALOG -> {
|
BookInfoScreen.DELETE_DIALOG -> {
|
||||||
BookInfoDeleteDialog(
|
BookInfoDeleteDialog(
|
||||||
actionDeleteDialog = actionDeleteDialog,
|
actionDeleteDialog = actionDeleteDialog,
|
||||||
dismissDialog = dismissDialog,
|
dismissDialog = dismissDialog
|
||||||
navigateBack = navigateBack
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Book's Story — free and open-source Material You eBook reader.
|
||||||
|
* Copyright (C) 2024-2025 Acclorite
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEffect
|
||||||
|
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
||||||
|
import ua.acclorite.book_story.presentation.reader.ReaderScreen
|
||||||
|
import ua.acclorite.book_story.presentation.settings.LibrarySettingsScreen
|
||||||
|
import ua.acclorite.book_story.ui.common.helpers.showToast
|
||||||
|
import ua.acclorite.book_story.ui.navigator.LocalNavigator
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BookInfoEffects(effects: SharedFlow<BookInfoEffect>, book: Book) {
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(effects, context, navigator, book) {
|
||||||
|
effects.collect { effect ->
|
||||||
|
when (effect) {
|
||||||
|
is BookInfoEffect.OnChangedCover -> {
|
||||||
|
context.getString(R.string.cover_image_changed)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnErrorResetCover -> {
|
||||||
|
context.getString(R.string.error_could_not_reset_cover)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnResetCover -> {
|
||||||
|
context.getString(R.string.cover_reset)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnDeletedCover -> {
|
||||||
|
context.getString(R.string.cover_image_deleted)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnTitleChanged -> {
|
||||||
|
context.getString(R.string.title_changed)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnAuthorChanged -> {
|
||||||
|
context.getString(R.string.author_changed)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnDescriptionChanged -> {
|
||||||
|
context.getString(R.string.description_changed)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnPathChanged -> {
|
||||||
|
context.getString(R.string.path_changed)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnBookDeleted -> {
|
||||||
|
context.getString(R.string.book_deleted)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnBookMoved -> {
|
||||||
|
context.getString(R.string.book_moved)
|
||||||
|
.showToast(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnNavigateBack -> {
|
||||||
|
navigator.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnNavigateToLibrarySettings -> {
|
||||||
|
navigator.push(LibrarySettingsScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BookInfoEffect.OnNavigateToReader -> {
|
||||||
|
if (book.id != -1) {
|
||||||
|
HistoryScreen.insertHistoryChannel.trySend(book.id)
|
||||||
|
navigator.push(ReaderScreen(book.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ fun BookInfoLayout(
|
||||||
showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit,
|
showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit,
|
||||||
showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit,
|
showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit,
|
||||||
showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit,
|
showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit,
|
||||||
navigateToReader: () -> Unit
|
navigateToReader: (BookInfoEvent.OnNavigateToReader) -> Unit
|
||||||
) {
|
) {
|
||||||
LazyColumnWithScrollbar(
|
LazyColumnWithScrollbar(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,13 @@ import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.core.helpers.calculateProgress
|
import ua.acclorite.book_story.core.helpers.calculateProgress
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
import ua.acclorite.book_story.ui.common.components.common.StyledText
|
import ua.acclorite.book_story.ui.common.components.common.StyledText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoLayoutButton(
|
fun BookInfoLayoutButton(
|
||||||
book: Book,
|
book: Book,
|
||||||
navigateToReader: () -> Unit
|
navigateToReader: (BookInfoEvent.OnNavigateToReader) -> Unit
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -30,9 +31,7 @@ fun BookInfoLayoutButton(
|
||||||
.padding(horizontal = 18.dp),
|
.padding(horizontal = 18.dp),
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (book.id != -1) {
|
navigateToReader(BookInfoEvent.OnNavigateToReader)
|
||||||
navigateToReader()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
StyledText(
|
StyledText(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
|
@ -31,9 +30,8 @@ fun BookInfoMoveDialog(
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
actionMoveDialog: (BookInfoEvent.OnActionMoveDialog) -> Unit,
|
actionMoveDialog: (BookInfoEvent.OnActionMoveDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
|
||||||
navigateToLibrarySettings: () -> Unit
|
navigateToLibrarySettings: (BookInfoEvent.OnNavigateToLibrarySettings) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val selectedCategories = remember {
|
val selectedCategories = remember {
|
||||||
mutableStateListOf<Category>().apply {
|
mutableStateListOf<Category>().apply {
|
||||||
clear()
|
clear()
|
||||||
|
|
@ -53,14 +51,13 @@ fun BookInfoMoveDialog(
|
||||||
onAction = {
|
onAction = {
|
||||||
actionMoveDialog(
|
actionMoveDialog(
|
||||||
BookInfoEvent.OnActionMoveDialog(
|
BookInfoEvent.OnActionMoveDialog(
|
||||||
selectedCategories = selectedCategories,
|
selectedCategories = selectedCategories
|
||||||
context = context
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
secondaryAction = stringResource(id = R.string.edit),
|
secondaryAction = stringResource(id = R.string.edit),
|
||||||
onSecondaryAction = {
|
onSecondaryAction = {
|
||||||
navigateToLibrarySettings()
|
navigateToLibrarySettings(BookInfoEvent.OnNavigateToLibrarySettings)
|
||||||
dismissDialog(BookInfoEvent.OnDismissDialog)
|
dismissDialog(BookInfoEvent.OnDismissDialog)
|
||||||
},
|
},
|
||||||
withContent = true,
|
withContent = true,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
package ua.acclorite.book_story.ui.book_info
|
package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
import ua.acclorite.book_story.ui.common.components.dialog.DialogWithTextField
|
import ua.acclorite.book_story.ui.common.components.dialog.DialogWithTextField
|
||||||
|
|
@ -18,7 +17,6 @@ fun BookInfoPathDialog(
|
||||||
actionPathDialog: (BookInfoEvent.OnActionPathDialog) -> Unit,
|
actionPathDialog: (BookInfoEvent.OnActionPathDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
DialogWithTextField(
|
DialogWithTextField(
|
||||||
initialValue = book.filePath.trim(),
|
initialValue = book.filePath.trim(),
|
||||||
lengthLimit = 10000,
|
lengthLimit = 10000,
|
||||||
|
|
@ -29,8 +27,7 @@ fun BookInfoPathDialog(
|
||||||
if (it.isBlank()) return@DialogWithTextField
|
if (it.isBlank()) return@DialogWithTextField
|
||||||
actionPathDialog(
|
actionPathDialog(
|
||||||
BookInfoEvent.OnActionPathDialog(
|
BookInfoEvent.OnActionPathDialog(
|
||||||
path = it.trim().replace("\n", ""),
|
path = it.trim().replace("\n", "")
|
||||||
context = context
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ fun BookInfoScaffold(
|
||||||
showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit,
|
showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit,
|
||||||
showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit,
|
showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit,
|
||||||
showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit,
|
showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit,
|
||||||
navigateToReader: () -> Unit,
|
navigateToReader: (BookInfoEvent.OnNavigateToReader) -> Unit,
|
||||||
navigateBack: () -> Unit
|
navigateBack: (BookInfoEvent.OnNavigateBack) -> Unit
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
package ua.acclorite.book_story.ui.book_info
|
package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
import ua.acclorite.book_story.ui.common.components.dialog.DialogWithTextField
|
import ua.acclorite.book_story.ui.common.components.dialog.DialogWithTextField
|
||||||
|
|
@ -18,7 +17,6 @@ fun BookInfoTitleDialog(
|
||||||
actionTitleDialog: (BookInfoEvent.OnActionTitleDialog) -> Unit,
|
actionTitleDialog: (BookInfoEvent.OnActionTitleDialog) -> Unit,
|
||||||
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
DialogWithTextField(
|
DialogWithTextField(
|
||||||
initialValue = book.title,
|
initialValue = book.title,
|
||||||
lengthLimit = 100,
|
lengthLimit = 100,
|
||||||
|
|
@ -29,8 +27,7 @@ fun BookInfoTitleDialog(
|
||||||
if (it.isBlank()) return@DialogWithTextField
|
if (it.isBlank()) return@DialogWithTextField
|
||||||
actionTitleDialog(
|
actionTitleDialog(
|
||||||
BookInfoEvent.OnActionTitleDialog(
|
BookInfoEvent.OnActionTitleDialog(
|
||||||
title = it.trim().replace("\n", ""),
|
title = it.trim().replace("\n", "")
|
||||||
context = context
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ fun BookInfoTopBar(
|
||||||
book: Book,
|
book: Book,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit,
|
showDetailsBottomSheet: (BookInfoEvent.OnShowDetailsBottomSheet) -> Unit,
|
||||||
navigateBack: () -> Unit
|
navigateBack: (BookInfoEvent.OnNavigateBack) -> Unit
|
||||||
) {
|
) {
|
||||||
val firstVisibleItemIndex = remember {
|
val firstVisibleItemIndex = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
|
@ -49,7 +49,7 @@ fun BookInfoTopBar(
|
||||||
contentID = 0,
|
contentID = 0,
|
||||||
contentNavigationIcon = {
|
contentNavigationIcon = {
|
||||||
NavigatorBackIconButton {
|
NavigatorBackIconButton {
|
||||||
navigateBack()
|
navigateBack(BookInfoEvent.OnNavigateBack)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contentTitle = {
|
contentTitle = {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ fun HistoryEffects(
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(effects, context, navigator, focusRequester, snackbarState) {
|
||||||
effects.collect { effect ->
|
effects.collect { effect ->
|
||||||
when (effect) {
|
when (effect) {
|
||||||
is HistoryEffect.OnRequestFocus -> {
|
is HistoryEffect.OnRequestFocus -> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue