refactor: library viewmodel
This commit is contained in:
parent
d50b1a5f9a
commit
3817cc1980
13 changed files with 283 additions and 223 deletions
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.library
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed class LibraryEffect {
|
||||
data object OnRequestFocus : LibraryEffect()
|
||||
data object OnBooksMoved : LibraryEffect()
|
||||
data object OnBooksDeleted : LibraryEffect()
|
||||
data object OnNavigateToLibrarySettings : LibraryEffect()
|
||||
data object OnNavigateToBrowse : LibraryEffect()
|
||||
data class OnNavigateToBookInfo(val id: Int) : LibraryEffect()
|
||||
data class OnNavigateToReader(val id: Int) : LibraryEffect()
|
||||
}
|
||||
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
package ua.acclorite.book_story.presentation.library
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import ua.acclorite.book_story.domain.model.library.Category
|
||||
|
||||
@Immutable
|
||||
|
|
@ -28,9 +26,7 @@ sealed class LibraryEvent {
|
|||
|
||||
data object OnSearch : LibraryEvent()
|
||||
|
||||
data class OnRequestFocus(
|
||||
val focusRequester: FocusRequester
|
||||
) : LibraryEvent()
|
||||
data object OnRequestFocus : LibraryEvent()
|
||||
|
||||
data object OnClearSelectedBooks : LibraryEvent()
|
||||
|
||||
|
|
@ -42,19 +38,28 @@ sealed class LibraryEvent {
|
|||
data object OnShowMoveDialog : LibraryEvent()
|
||||
|
||||
data class OnActionMoveDialog(
|
||||
val selectedCategories: List<Category>,
|
||||
val context: Context
|
||||
val selectedCategories: List<Category>
|
||||
) : LibraryEvent()
|
||||
|
||||
data object OnShowDeleteDialog : LibraryEvent()
|
||||
|
||||
data class OnActionDeleteDialog(
|
||||
val context: Context
|
||||
) : LibraryEvent()
|
||||
data object OnActionDeleteDialog : LibraryEvent()
|
||||
|
||||
data object OnDismissDialog : LibraryEvent()
|
||||
|
||||
data object OnShowFilterBottomSheet : LibraryEvent()
|
||||
|
||||
data object OnDismissBottomSheet : LibraryEvent()
|
||||
|
||||
data object OnNavigateToLibrarySettings : LibraryEvent()
|
||||
|
||||
data object OnNavigateToBrowse : LibraryEvent()
|
||||
|
||||
data class OnNavigateToBookInfo(
|
||||
val id: Int
|
||||
) : LibraryEvent()
|
||||
|
||||
data class OnNavigateToReader(
|
||||
val id: Int
|
||||
) : LibraryEvent()
|
||||
}
|
||||
|
|
@ -12,7 +12,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
|
|
@ -20,17 +23,14 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.domain.model.library.Book
|
||||
import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase
|
||||
import ua.acclorite.book_story.domain.use_case.book.SearchBooksUseCase
|
||||
import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase
|
||||
import ua.acclorite.book_story.presentation.browse.BrowseScreen
|
||||
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
||||
import ua.acclorite.book_story.presentation.library.model.SelectableBook
|
||||
import ua.acclorite.book_story.ui.common.helpers.showToast
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@HiltViewModel
|
||||
class LibraryModel @Inject constructor(
|
||||
|
|
@ -44,22 +44,21 @@ class LibraryModel @Inject constructor(
|
|||
private val _state = MutableStateFlow(LibraryState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<LibraryEffect>()
|
||||
val effects = _effects.asSharedFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
onEvent(
|
||||
LibraryEvent.OnRefreshList(
|
||||
loading = true,
|
||||
hideSearch = true
|
||||
)
|
||||
onEvent(
|
||||
LibraryEvent.OnRefreshList(
|
||||
loading = true,
|
||||
hideSearch = true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/* Observe channel - - - - - - - - - - - */
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
LibraryScreen.refreshListChannel.receiveAsFlow().collectLatest {
|
||||
delay(it)
|
||||
yield()
|
||||
|
||||
onEvent(
|
||||
LibraryEvent.OnRefreshList(
|
||||
loading = false,
|
||||
|
|
@ -75,33 +74,42 @@ class LibraryModel @Inject constructor(
|
|||
private var searchQueryChange: Job? = null
|
||||
|
||||
fun onEvent(event: LibraryEvent) {
|
||||
when (event) {
|
||||
is LibraryEvent.OnRefreshList -> {
|
||||
refreshJob?.cancel()
|
||||
refreshJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = true,
|
||||
isLoading = event.loading,
|
||||
showSearch = if (event.hideSearch) false else it.showSearch
|
||||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
when (event) {
|
||||
is LibraryEvent.OnRefreshList -> {
|
||||
refreshJob?.cancel()
|
||||
refreshJob = viewModelScope.launch(Dispatchers.Default) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = true,
|
||||
isLoading = event.loading,
|
||||
showSearch = if (event.hideSearch) false else it.showSearch
|
||||
)
|
||||
}
|
||||
|
||||
yield()
|
||||
getBooksFromDatabase()
|
||||
ensureActive()
|
||||
val books = searchBooksUseCase(
|
||||
if (_state.value.showSearch) _state.value.searchQuery
|
||||
else ""
|
||||
).map { book -> SelectableBook(book, false) }
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = books,
|
||||
hasSelectedItems = false,
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
|
||||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
isLoading = false
|
||||
)
|
||||
delay(500) // Delay for UI smoothness
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnSearchVisibility -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
is LibraryEvent.OnSearchVisibility -> {
|
||||
if (!event.show) {
|
||||
onEvent(
|
||||
LibraryEvent.OnRefreshList(
|
||||
|
|
@ -109,41 +117,31 @@ class LibraryModel @Inject constructor(
|
|||
hideSearch = true
|
||||
)
|
||||
)
|
||||
} else {
|
||||
_state.update {
|
||||
it.copy(
|
||||
searchQuery = "",
|
||||
hasFocused = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
showSearch = event.show
|
||||
showSearch = event.show,
|
||||
searchQuery = if (event.show) "" else it.searchQuery,
|
||||
hasFocused = if (event.show) false else it.hasFocused
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnSearchQueryChange -> {
|
||||
viewModelScope.launch {
|
||||
is LibraryEvent.OnSearchQueryChange -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
searchQuery = event.query
|
||||
)
|
||||
}
|
||||
searchQueryChange?.cancel()
|
||||
searchQueryChange = launch(Dispatchers.IO) {
|
||||
searchQueryChange = viewModelScope.launch(Dispatchers.IO) {
|
||||
delay(500)
|
||||
yield()
|
||||
onEvent(LibraryEvent.OnSearch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnSearch -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
is LibraryEvent.OnSearch -> {
|
||||
onEvent(
|
||||
LibraryEvent.OnRefreshList(
|
||||
loading = false,
|
||||
|
|
@ -151,12 +149,10 @@ class LibraryModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnRequestFocus -> {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
is LibraryEvent.OnRequestFocus -> {
|
||||
if (!_state.value.hasFocused) {
|
||||
event.focusRequester.requestFocus()
|
||||
_effects.emit(LibraryEffect.OnRequestFocus)
|
||||
_state.update {
|
||||
it.copy(
|
||||
hasFocused = true
|
||||
|
|
@ -164,170 +160,152 @@ class LibraryModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnClearSelectedBooks -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = it.books.map { book -> book.copy(selected = false) },
|
||||
hasSelectedItems = false
|
||||
)
|
||||
is LibraryEvent.OnClearSelectedBooks -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = it.books.map { book -> book.copy(selected = false) },
|
||||
hasSelectedItems = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnSelectBook -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val editedList = _state.value.books.map {
|
||||
if (it.data.id == event.id) it.copy(selected = event.select ?: !it.selected)
|
||||
else it
|
||||
}
|
||||
is LibraryEvent.OnSelectBook -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
val editedList = _state.value.books.map {
|
||||
if (it.data.id == event.id) it.copy(
|
||||
selected = event.select ?: !it.selected
|
||||
)
|
||||
else it
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = editedList,
|
||||
selectedItemsCount = editedList.filter { book -> book.selected }.size,
|
||||
hasSelectedItems = editedList.any { book -> book.selected }
|
||||
)
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = editedList,
|
||||
selectedItemsCount = editedList.filter { book -> book.selected }.size,
|
||||
hasSelectedItems = editedList.any { book -> book.selected }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnShowMoveDialog -> {
|
||||
viewModelScope.launch {
|
||||
is LibraryEvent.OnShowMoveDialog -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = LibraryScreen.MOVE_DIALOG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnActionMoveDialog -> {
|
||||
viewModelScope.launch {
|
||||
_state.value.books.forEach { book ->
|
||||
if (!book.selected) return@forEach
|
||||
updateBookUseCase(
|
||||
book.data.copy(
|
||||
categories = event.selectedCategories.map { it.id }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = it.books.map { book ->
|
||||
if (!book.selected) return@map book
|
||||
book.copy(
|
||||
data = book.data.copy(
|
||||
categories = event.selectedCategories.map { it.id }
|
||||
),
|
||||
selected = false
|
||||
is LibraryEvent.OnActionMoveDialog -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
_state.value.books.forEach { book ->
|
||||
if (!book.selected) return@forEach
|
||||
updateBookUseCase(
|
||||
book.data.copy(
|
||||
categories = event.selectedCategories.map { it.id }
|
||||
)
|
||||
},
|
||||
hasSelectedItems = false,
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
HistoryScreen.refreshListChannel.trySend(0)
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = it.books.map { book ->
|
||||
if (!book.selected) return@map book
|
||||
book.copy(
|
||||
data = book.data.copy(
|
||||
categories = event.selectedCategories.map { it.id }
|
||||
),
|
||||
selected = false
|
||||
)
|
||||
},
|
||||
hasSelectedItems = false,
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
event.context
|
||||
.getString(R.string.books_moved)
|
||||
.showToast(context = event.context)
|
||||
HistoryScreen.refreshListChannel.trySend(0)
|
||||
_effects.emit(LibraryEffect.OnBooksMoved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnShowDeleteDialog -> {
|
||||
viewModelScope.launch {
|
||||
is LibraryEvent.OnShowDeleteDialog -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = LibraryScreen.DELETE_DIALOG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnActionDeleteDialog -> {
|
||||
viewModelScope.launch {
|
||||
_state.value.books.forEach {
|
||||
if (!it.selected) return@forEach
|
||||
deleteBookUseCase(it.data)
|
||||
}
|
||||
is LibraryEvent.OnActionDeleteDialog -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
_state.value.books.forEach {
|
||||
if (!it.selected) return@forEach
|
||||
deleteBookUseCase(it.data)
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = it.books.filter { book -> !book.selected },
|
||||
hasSelectedItems = false,
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = it.books.filter { book -> !book.selected },
|
||||
hasSelectedItems = false,
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
|
||||
HistoryScreen.refreshListChannel.trySend(0)
|
||||
BrowseScreen.refreshListChannel.trySend(Unit)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
event.context
|
||||
.getString(R.string.books_deleted)
|
||||
.showToast(context = event.context)
|
||||
HistoryScreen.refreshListChannel.trySend(0)
|
||||
BrowseScreen.refreshListChannel.trySend(Unit)
|
||||
_effects.emit(LibraryEffect.OnBooksDeleted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnDismissDialog -> {
|
||||
viewModelScope.launch {
|
||||
is LibraryEvent.OnDismissDialog -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnShowFilterBottomSheet -> {
|
||||
viewModelScope.launch {
|
||||
is LibraryEvent.OnShowFilterBottomSheet -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
bottomSheet = LibraryScreen.FILTER_BOTTOM_SHEET
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnDismissBottomSheet -> {
|
||||
viewModelScope.launch {
|
||||
is LibraryEvent.OnDismissBottomSheet -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
bottomSheet = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is LibraryEvent.OnNavigateToLibrarySettings -> {
|
||||
_effects.emit(LibraryEffect.OnNavigateToLibrarySettings)
|
||||
}
|
||||
|
||||
is LibraryEvent.OnNavigateToBrowse -> {
|
||||
_effects.emit(LibraryEffect.OnNavigateToBrowse)
|
||||
}
|
||||
|
||||
is LibraryEvent.OnNavigateToBookInfo -> {
|
||||
_effects.emit(LibraryEffect.OnNavigateToBookInfo(event.id))
|
||||
}
|
||||
|
||||
is LibraryEvent.OnNavigateToReader -> {
|
||||
_effects.emit(LibraryEffect.OnNavigateToReader(event.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getBooksFromDatabase(
|
||||
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
|
||||
) {
|
||||
val books = searchBooksUseCase(query)
|
||||
.sortedWith(compareByDescending<Book> { it.lastOpened }.thenBy { it.title })
|
||||
.map { book -> SelectableBook(book, false) }
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
books = books,
|
||||
hasSelectedItems = false,
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
|
||||
mutex.withLock {
|
||||
yield()
|
||||
coroutineContext.ensureActive()
|
||||
this.value = function(this.value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,11 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
|
||||
import ua.acclorite.book_story.presentation.browse.BrowseScreen
|
||||
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
||||
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.ui.common.helpers.LocalSettings
|
||||
import ua.acclorite.book_story.ui.library.LibraryContent
|
||||
import ua.acclorite.book_story.ui.navigator.LocalNavigator
|
||||
import ua.acclorite.book_story.ui.library.LibraryEffects
|
||||
|
||||
@Parcelize
|
||||
object LibraryScreen : Screen, Parcelable {
|
||||
|
|
@ -58,7 +53,6 @@ object LibraryScreen : Screen, Parcelable {
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.current
|
||||
val screenModel = hiltViewModel<LibraryModel>()
|
||||
val settingsModel = hiltViewModel<SettingsModel>()
|
||||
val settings = LocalSettings.current
|
||||
|
|
@ -105,6 +99,11 @@ object LibraryScreen : Screen, Parcelable {
|
|||
}
|
||||
}
|
||||
|
||||
LibraryEffects(
|
||||
effects = screenModel.effects,
|
||||
focusRequester = focusRequester
|
||||
)
|
||||
|
||||
LibraryContent(
|
||||
books = state.value.books,
|
||||
selectedItemsCount = state.value.selectedItemsCount,
|
||||
|
|
@ -150,19 +149,10 @@ object LibraryScreen : Screen, Parcelable {
|
|||
showFilterBottomSheet = screenModel::onEvent,
|
||||
dismissBottomSheet = screenModel::onEvent,
|
||||
dismissDialog = screenModel::onEvent,
|
||||
navigateToBrowse = {
|
||||
navigator.push(BrowseScreen)
|
||||
},
|
||||
navigateToReader = {
|
||||
HistoryScreen.insertHistoryChannel.trySend(it)
|
||||
navigator.push(ReaderScreen(it))
|
||||
},
|
||||
navigateToBookInfo = {
|
||||
navigator.push(BookInfoScreen(bookId = it))
|
||||
},
|
||||
navigateToLibrarySettings = {
|
||||
navigator.push(LibrarySettingsScreen)
|
||||
}
|
||||
navigateToBrowse = screenModel::onEvent,
|
||||
navigateToReader = screenModel::onEvent,
|
||||
navigateToBookInfo = screenModel::onEvent,
|
||||
navigateToLibrarySettings = screenModel::onEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -69,10 +69,10 @@ fun LibraryContent(
|
|||
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit,
|
||||
showFilterBottomSheet: (LibraryEvent.OnShowFilterBottomSheet) -> Unit,
|
||||
dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit,
|
||||
navigateToBrowse: () -> Unit,
|
||||
navigateToBookInfo: (id: Int) -> Unit,
|
||||
navigateToReader: (id: Int) -> Unit,
|
||||
navigateToLibrarySettings: () -> Unit
|
||||
navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit,
|
||||
navigateToBookInfo: (LibraryEvent.OnNavigateToBookInfo) -> Unit,
|
||||
navigateToReader: (LibraryEvent.OnNavigateToReader) -> Unit,
|
||||
navigateToLibrarySettings: (LibraryEvent.OnNavigateToLibrarySettings) -> Unit
|
||||
) {
|
||||
LibraryDialog(
|
||||
dialog = dialog,
|
||||
|
|
@ -146,6 +146,6 @@ fun LibraryContent(
|
|||
pagerState = pagerState,
|
||||
doublePressExit = doublePressExit,
|
||||
clearSelectedBooks = clearSelectedBooks,
|
||||
searchVisibility = searchVisibility,
|
||||
searchVisibility = searchVisibility
|
||||
)
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ package ua.acclorite.book_story.ui.library
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.presentation.library.LibraryEvent
|
||||
|
|
@ -21,8 +20,6 @@ fun LibraryDeleteDialog(
|
|||
actionDeleteDialog: (LibraryEvent.OnActionDeleteDialog) -> Unit,
|
||||
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Dialog(
|
||||
title = stringResource(id = R.string.delete_books),
|
||||
icon = Icons.Outlined.DeleteOutline,
|
||||
|
|
@ -35,11 +32,7 @@ fun LibraryDeleteDialog(
|
|||
dismissDialog(LibraryEvent.OnDismissDialog)
|
||||
},
|
||||
onAction = {
|
||||
actionDeleteDialog(
|
||||
LibraryEvent.OnActionDeleteDialog(
|
||||
context = context
|
||||
)
|
||||
)
|
||||
actionDeleteDialog(LibraryEvent.OnActionDeleteDialog)
|
||||
},
|
||||
withContent = false
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ fun LibraryDialog(
|
|||
actionMoveDialog: (LibraryEvent.OnActionMoveDialog) -> Unit,
|
||||
actionDeleteDialog: (LibraryEvent.OnActionDeleteDialog) -> Unit,
|
||||
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit,
|
||||
navigateToLibrarySettings: () -> Unit
|
||||
navigateToLibrarySettings: (LibraryEvent.OnNavigateToLibrarySettings) -> Unit
|
||||
) {
|
||||
when (dialog) {
|
||||
LibraryScreen.MOVE_DIALOG -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.library
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
|
||||
import ua.acclorite.book_story.presentation.browse.BrowseScreen
|
||||
import ua.acclorite.book_story.presentation.history.HistoryScreen
|
||||
import ua.acclorite.book_story.presentation.library.LibraryEffect
|
||||
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 LibraryEffects(effects: SharedFlow<LibraryEffect>, focusRequester: FocusRequester) {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.current
|
||||
|
||||
LaunchedEffect(effects, focusRequester, context, navigator) {
|
||||
effects.collect { effect ->
|
||||
when (effect) {
|
||||
is LibraryEffect.OnRequestFocus -> {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
|
||||
is LibraryEffect.OnBooksMoved -> {
|
||||
context.getString(R.string.books_moved)
|
||||
.showToast(context = context)
|
||||
}
|
||||
|
||||
is LibraryEffect.OnBooksDeleted -> {
|
||||
context.getString(R.string.books_deleted)
|
||||
.showToast(context = context)
|
||||
}
|
||||
|
||||
is LibraryEffect.OnNavigateToLibrarySettings -> {
|
||||
navigator.push(LibrarySettingsScreen)
|
||||
}
|
||||
|
||||
is LibraryEffect.OnNavigateToBrowse -> {
|
||||
navigator.push(BrowseScreen)
|
||||
}
|
||||
|
||||
is LibraryEffect.OnNavigateToBookInfo -> {
|
||||
navigator.push(BookInfoScreen(bookId = effect.id))
|
||||
}
|
||||
|
||||
is LibraryEffect.OnNavigateToReader -> {
|
||||
HistoryScreen.insertHistoryChannel.trySend(effect.id)
|
||||
navigator.push(ReaderScreen(effect.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.presentation.library.LibraryEvent
|
||||
import ua.acclorite.book_story.ui.common.components.placeholder.EmptyPlaceholder
|
||||
import ua.acclorite.book_story.ui.theme.Transitions
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ fun BoxScope.LibraryEmptyPlaceholder(
|
|||
isLoading: Boolean,
|
||||
isRefreshing: Boolean,
|
||||
isBooksEmpty: Boolean,
|
||||
navigateToBrowse: () -> Unit
|
||||
navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !isLoading
|
||||
|
|
@ -40,7 +41,7 @@ fun BoxScope.LibraryEmptyPlaceholder(
|
|||
modifier = Modifier.align(Alignment.Center),
|
||||
actionTitle = stringResource(id = R.string.add_book)
|
||||
) {
|
||||
navigateToBrowse()
|
||||
navigateToBrowse(LibraryEvent.OnNavigateToBrowse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.derivedStateOf
|
|||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ua.acclorite.book_story.R
|
||||
|
|
@ -31,10 +30,8 @@ fun LibraryMoveDialog(
|
|||
categories: List<Category>,
|
||||
actionMoveDialog: (LibraryEvent.OnActionMoveDialog) -> Unit,
|
||||
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit,
|
||||
navigateToLibrarySettings: () -> Unit
|
||||
navigateToLibrarySettings: (LibraryEvent.OnNavigateToLibrarySettings) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val selectedBooks = remember {
|
||||
derivedStateOf {
|
||||
books.filter { it.selected }
|
||||
|
|
@ -61,14 +58,13 @@ fun LibraryMoveDialog(
|
|||
onAction = {
|
||||
actionMoveDialog(
|
||||
LibraryEvent.OnActionMoveDialog(
|
||||
selectedCategories = selectedCategories,
|
||||
context = context
|
||||
selectedCategories = selectedCategories
|
||||
)
|
||||
)
|
||||
},
|
||||
secondaryAction = stringResource(id = R.string.edit),
|
||||
onSecondaryAction = {
|
||||
navigateToLibrarySettings()
|
||||
navigateToLibrarySettings(LibraryEvent.OnNavigateToLibrarySettings)
|
||||
dismissDialog(LibraryEvent.OnDismissDialog)
|
||||
},
|
||||
withContent = true,
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ fun LibraryPager(
|
|||
isLoading: Boolean,
|
||||
isRefreshing: Boolean,
|
||||
selectBook: (LibraryEvent.OnSelectBook) -> Unit,
|
||||
navigateToBrowse: () -> Unit,
|
||||
navigateToBookInfo: (id: Int) -> Unit,
|
||||
navigateToReader: (id: Int) -> Unit,
|
||||
navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit,
|
||||
navigateToBookInfo: (LibraryEvent.OnNavigateToBookInfo) -> Unit,
|
||||
navigateToReader: (LibraryEvent.OnNavigateToReader) -> Unit,
|
||||
) {
|
||||
val categorizedBooks = remember(
|
||||
books,
|
||||
|
|
@ -172,8 +172,20 @@ fun LibraryPager(
|
|||
)
|
||||
)
|
||||
},
|
||||
navigateToBookInfo = { navigateToBookInfo(book.data.id) },
|
||||
navigateToReader = { navigateToReader(book.data.id) },
|
||||
navigateToBookInfo = {
|
||||
navigateToBookInfo(
|
||||
LibraryEvent.OnNavigateToBookInfo(
|
||||
book.data.id
|
||||
)
|
||||
)
|
||||
},
|
||||
navigateToReader = {
|
||||
navigateToReader(
|
||||
LibraryEvent.OnNavigateToReader(
|
||||
book.data.id
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ fun LibraryScaffold(
|
|||
showMoveDialog: (LibraryEvent.OnShowMoveDialog) -> Unit,
|
||||
showDeleteDialog: (LibraryEvent.OnShowDeleteDialog) -> Unit,
|
||||
showFilterBottomSheet: (LibraryEvent.OnShowFilterBottomSheet) -> Unit,
|
||||
navigateToBrowse: () -> Unit,
|
||||
navigateToBookInfo: (id: Int) -> Unit,
|
||||
navigateToReader: (id: Int) -> Unit,
|
||||
navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit,
|
||||
navigateToBookInfo: (LibraryEvent.OnNavigateToBookInfo) -> Unit,
|
||||
navigateToReader: (LibraryEvent.OnNavigateToReader) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ fun LibraryTopBar(
|
|||
modifier = Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.onGloballyPositioned {
|
||||
requestFocus(LibraryEvent.OnRequestFocus(focusRequester))
|
||||
requestFocus(LibraryEvent.OnRequestFocus)
|
||||
},
|
||||
initialQuery = searchQuery,
|
||||
onQueryChange = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue