refactor: library viewmodel

This commit is contained in:
Acclorite 2025-08-16 22:41:02 +03:00
parent d50b1a5f9a
commit 3817cc1980
No known key found for this signature in database
GPG key ID: 6E54C611F6EE8593
13 changed files with 283 additions and 223 deletions

View file

@ -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()
}

View file

@ -6,9 +6,7 @@
package ua.acclorite.book_story.presentation.library package ua.acclorite.book_story.presentation.library
import android.content.Context
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.focus.FocusRequester
import ua.acclorite.book_story.domain.model.library.Category import ua.acclorite.book_story.domain.model.library.Category
@Immutable @Immutable
@ -28,9 +26,7 @@ sealed class LibraryEvent {
data object OnSearch : LibraryEvent() data object OnSearch : LibraryEvent()
data class OnRequestFocus( data object OnRequestFocus : LibraryEvent()
val focusRequester: FocusRequester
) : LibraryEvent()
data object OnClearSelectedBooks : LibraryEvent() data object OnClearSelectedBooks : LibraryEvent()
@ -42,19 +38,28 @@ sealed class LibraryEvent {
data object OnShowMoveDialog : LibraryEvent() data object OnShowMoveDialog : LibraryEvent()
data class OnActionMoveDialog( data class OnActionMoveDialog(
val selectedCategories: List<Category>, val selectedCategories: List<Category>
val context: Context
) : LibraryEvent() ) : LibraryEvent()
data object OnShowDeleteDialog : LibraryEvent() data object OnShowDeleteDialog : LibraryEvent()
data class OnActionDeleteDialog( data object OnActionDeleteDialog : LibraryEvent()
val context: Context
) : LibraryEvent()
data object OnDismissDialog : LibraryEvent() data object OnDismissDialog : LibraryEvent()
data object OnShowFilterBottomSheet : LibraryEvent() data object OnShowFilterBottomSheet : LibraryEvent()
data object OnDismissBottomSheet : 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()
} }

View file

@ -12,7 +12,10 @@ 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.delay import kotlinx.coroutines.delay
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.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
@ -20,17 +23,14 @@ 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.model.library.Book
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.SearchBooksUseCase 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.domain.use_case.book.UpdateBookUseCase
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.model.SelectableBook import ua.acclorite.book_story.presentation.library.model.SelectableBook
import ua.acclorite.book_story.ui.common.helpers.showToast
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.coroutineContext
@HiltViewModel @HiltViewModel
class LibraryModel @Inject constructor( class LibraryModel @Inject constructor(
@ -44,22 +44,21 @@ class LibraryModel @Inject constructor(
private val _state = MutableStateFlow(LibraryState()) private val _state = MutableStateFlow(LibraryState())
val state = _state.asStateFlow() val state = _state.asStateFlow()
private val _effects = MutableSharedFlow<LibraryEffect>()
val effects = _effects.asSharedFlow()
init { init {
viewModelScope.launch(Dispatchers.IO) {
onEvent( onEvent(
LibraryEvent.OnRefreshList( LibraryEvent.OnRefreshList(
loading = true, loading = true,
hideSearch = true hideSearch = true
) )
) )
}
/* Observe channel - - - - - - - - - - - */ /* Observe channel - - - - - - - - - - - */
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch {
LibraryScreen.refreshListChannel.receiveAsFlow().collectLatest { LibraryScreen.refreshListChannel.receiveAsFlow().collectLatest {
delay(it) delay(it)
yield()
onEvent( onEvent(
LibraryEvent.OnRefreshList( LibraryEvent.OnRefreshList(
loading = false, loading = false,
@ -75,10 +74,11 @@ class LibraryModel @Inject constructor(
private var searchQueryChange: Job? = null private var searchQueryChange: Job? = null
fun onEvent(event: LibraryEvent) { fun onEvent(event: LibraryEvent) {
viewModelScope.launch {
when (event) { when (event) {
is LibraryEvent.OnRefreshList -> { is LibraryEvent.OnRefreshList -> {
refreshJob?.cancel() refreshJob?.cancel()
refreshJob = viewModelScope.launch(Dispatchers.IO) { refreshJob = viewModelScope.launch(Dispatchers.Default) {
_state.update { _state.update {
it.copy( it.copy(
isRefreshing = true, isRefreshing = true,
@ -87,21 +87,29 @@ class LibraryModel @Inject constructor(
) )
} }
yield() ensureActive()
getBooksFromDatabase() val books = searchBooksUseCase(
if (_state.value.showSearch) _state.value.searchQuery
delay(500) else ""
).map { book -> SelectableBook(book, false) }
_state.update { _state.update {
it.copy( it.copy(
isRefreshing = false, books = books,
hasSelectedItems = false,
isLoading = false isLoading = false
) )
} }
delay(500) // Delay for UI smoothness
_state.update {
it.copy(
isRefreshing = false
)
}
} }
} }
is LibraryEvent.OnSearchVisibility -> { is LibraryEvent.OnSearchVisibility -> {
viewModelScope.launch(Dispatchers.IO) {
if (!event.show) { if (!event.show) {
onEvent( onEvent(
LibraryEvent.OnRefreshList( LibraryEvent.OnRefreshList(
@ -109,41 +117,31 @@ class LibraryModel @Inject constructor(
hideSearch = true hideSearch = true
) )
) )
} else {
_state.update {
it.copy(
searchQuery = "",
hasFocused = false
)
}
} }
_state.update { _state.update {
it.copy( 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 -> { is LibraryEvent.OnSearchQueryChange -> {
viewModelScope.launch {
_state.update { _state.update {
it.copy( it.copy(
searchQuery = event.query searchQuery = event.query
) )
} }
searchQueryChange?.cancel() searchQueryChange?.cancel()
searchQueryChange = launch(Dispatchers.IO) { searchQueryChange = viewModelScope.launch(Dispatchers.IO) {
delay(500) delay(500)
yield()
onEvent(LibraryEvent.OnSearch) onEvent(LibraryEvent.OnSearch)
} }
} }
}
is LibraryEvent.OnSearch -> { is LibraryEvent.OnSearch -> {
viewModelScope.launch(Dispatchers.IO) {
onEvent( onEvent(
LibraryEvent.OnRefreshList( LibraryEvent.OnRefreshList(
loading = false, loading = false,
@ -151,12 +149,10 @@ class LibraryModel @Inject constructor(
) )
) )
} }
}
is LibraryEvent.OnRequestFocus -> { is LibraryEvent.OnRequestFocus -> {
viewModelScope.launch(Dispatchers.Main) {
if (!_state.value.hasFocused) { if (!_state.value.hasFocused) {
event.focusRequester.requestFocus() _effects.emit(LibraryEffect.OnRequestFocus)
_state.update { _state.update {
it.copy( it.copy(
hasFocused = true hasFocused = true
@ -164,10 +160,9 @@ class LibraryModel @Inject constructor(
} }
} }
} }
}
is LibraryEvent.OnClearSelectedBooks -> { is LibraryEvent.OnClearSelectedBooks -> {
viewModelScope.launch(Dispatchers.IO) { withContext(Dispatchers.Default) {
_state.update { _state.update {
it.copy( it.copy(
books = it.books.map { book -> book.copy(selected = false) }, books = it.books.map { book -> book.copy(selected = false) },
@ -178,9 +173,11 @@ class LibraryModel @Inject constructor(
} }
is LibraryEvent.OnSelectBook -> { is LibraryEvent.OnSelectBook -> {
viewModelScope.launch(Dispatchers.IO) { withContext(Dispatchers.Default) {
val editedList = _state.value.books.map { val editedList = _state.value.books.map {
if (it.data.id == event.id) it.copy(selected = event.select ?: !it.selected) if (it.data.id == event.id) it.copy(
selected = event.select ?: !it.selected
)
else it else it
} }
@ -195,17 +192,15 @@ class LibraryModel @Inject constructor(
} }
is LibraryEvent.OnShowMoveDialog -> { is LibraryEvent.OnShowMoveDialog -> {
viewModelScope.launch {
_state.update { _state.update {
it.copy( it.copy(
dialog = LibraryScreen.MOVE_DIALOG dialog = LibraryScreen.MOVE_DIALOG
) )
} }
} }
}
is LibraryEvent.OnActionMoveDialog -> { is LibraryEvent.OnActionMoveDialog -> {
viewModelScope.launch { withContext(Dispatchers.Default) {
_state.value.books.forEach { book -> _state.value.books.forEach { book ->
if (!book.selected) return@forEach if (!book.selected) return@forEach
updateBookUseCase( updateBookUseCase(
@ -232,27 +227,20 @@ class LibraryModel @Inject constructor(
} }
HistoryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0)
_effects.emit(LibraryEffect.OnBooksMoved)
withContext(Dispatchers.Main) {
event.context
.getString(R.string.books_moved)
.showToast(context = event.context)
}
} }
} }
is LibraryEvent.OnShowDeleteDialog -> { is LibraryEvent.OnShowDeleteDialog -> {
viewModelScope.launch {
_state.update { _state.update {
it.copy( it.copy(
dialog = LibraryScreen.DELETE_DIALOG dialog = LibraryScreen.DELETE_DIALOG
) )
} }
} }
}
is LibraryEvent.OnActionDeleteDialog -> { is LibraryEvent.OnActionDeleteDialog -> {
viewModelScope.launch { withContext(Dispatchers.Default) {
_state.value.books.forEach { _state.value.books.forEach {
if (!it.selected) return@forEach if (!it.selected) return@forEach
deleteBookUseCase(it.data) deleteBookUseCase(it.data)
@ -268,66 +256,56 @@ class LibraryModel @Inject constructor(
HistoryScreen.refreshListChannel.trySend(0) HistoryScreen.refreshListChannel.trySend(0)
BrowseScreen.refreshListChannel.trySend(Unit) BrowseScreen.refreshListChannel.trySend(Unit)
_effects.emit(LibraryEffect.OnBooksDeleted)
withContext(Dispatchers.Main) {
event.context
.getString(R.string.books_deleted)
.showToast(context = event.context)
}
} }
} }
is LibraryEvent.OnDismissDialog -> { is LibraryEvent.OnDismissDialog -> {
viewModelScope.launch {
_state.update { _state.update {
it.copy( it.copy(
dialog = null dialog = null
) )
} }
} }
}
is LibraryEvent.OnShowFilterBottomSheet -> { is LibraryEvent.OnShowFilterBottomSheet -> {
viewModelScope.launch {
_state.update { _state.update {
it.copy( it.copy(
bottomSheet = LibraryScreen.FILTER_BOTTOM_SHEET bottomSheet = LibraryScreen.FILTER_BOTTOM_SHEET
) )
} }
} }
}
is LibraryEvent.OnDismissBottomSheet -> { is LibraryEvent.OnDismissBottomSheet -> {
viewModelScope.launch {
_state.update { _state.update {
it.copy( it.copy(
bottomSheet = null bottomSheet = null
) )
} }
} }
}
} is LibraryEvent.OnNavigateToLibrarySettings -> {
_effects.emit(LibraryEffect.OnNavigateToLibrarySettings)
} }
private suspend fun getBooksFromDatabase( is LibraryEvent.OnNavigateToBrowse -> {
query: String = if (_state.value.showSearch) _state.value.searchQuery else "" _effects.emit(LibraryEffect.OnNavigateToBrowse)
) { }
val books = searchBooksUseCase(query)
.sortedWith(compareByDescending<Book> { it.lastOpened }.thenBy { it.title })
.map { book -> SelectableBook(book, false) }
_state.update { is LibraryEvent.OnNavigateToBookInfo -> {
it.copy( _effects.emit(LibraryEffect.OnNavigateToBookInfo(event.id))
books = books, }
hasSelectedItems = false,
isLoading = false is LibraryEvent.OnNavigateToReader -> {
) _effects.emit(LibraryEffect.OnNavigateToReader(event.id))
}
}
} }
} }
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)
} }
} }

View file

@ -23,16 +23,11 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize 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.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.common.helpers.LocalSettings import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.library.LibraryContent 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 @Parcelize
object LibraryScreen : Screen, Parcelable { object LibraryScreen : Screen, Parcelable {
@ -58,7 +53,6 @@ object LibraryScreen : Screen, Parcelable {
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.current
val screenModel = hiltViewModel<LibraryModel>() val screenModel = hiltViewModel<LibraryModel>()
val settingsModel = hiltViewModel<SettingsModel>() val settingsModel = hiltViewModel<SettingsModel>()
val settings = LocalSettings.current val settings = LocalSettings.current
@ -105,6 +99,11 @@ object LibraryScreen : Screen, Parcelable {
} }
} }
LibraryEffects(
effects = screenModel.effects,
focusRequester = focusRequester
)
LibraryContent( LibraryContent(
books = state.value.books, books = state.value.books,
selectedItemsCount = state.value.selectedItemsCount, selectedItemsCount = state.value.selectedItemsCount,
@ -150,19 +149,10 @@ object LibraryScreen : Screen, Parcelable {
showFilterBottomSheet = screenModel::onEvent, showFilterBottomSheet = screenModel::onEvent,
dismissBottomSheet = screenModel::onEvent, dismissBottomSheet = screenModel::onEvent,
dismissDialog = screenModel::onEvent, dismissDialog = screenModel::onEvent,
navigateToBrowse = { navigateToBrowse = screenModel::onEvent,
navigator.push(BrowseScreen) navigateToReader = screenModel::onEvent,
}, navigateToBookInfo = screenModel::onEvent,
navigateToReader = { navigateToLibrarySettings = screenModel::onEvent
HistoryScreen.insertHistoryChannel.trySend(it)
navigator.push(ReaderScreen(it))
},
navigateToBookInfo = {
navigator.push(BookInfoScreen(bookId = it))
},
navigateToLibrarySettings = {
navigator.push(LibrarySettingsScreen)
}
) )
} }
} }

View file

@ -69,10 +69,10 @@ fun LibraryContent(
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit, dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit,
showFilterBottomSheet: (LibraryEvent.OnShowFilterBottomSheet) -> Unit, showFilterBottomSheet: (LibraryEvent.OnShowFilterBottomSheet) -> Unit,
dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit, dismissBottomSheet: (LibraryEvent.OnDismissBottomSheet) -> Unit,
navigateToBrowse: () -> Unit, navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit,
navigateToBookInfo: (id: Int) -> Unit, navigateToBookInfo: (LibraryEvent.OnNavigateToBookInfo) -> Unit,
navigateToReader: (id: Int) -> Unit, navigateToReader: (LibraryEvent.OnNavigateToReader) -> Unit,
navigateToLibrarySettings: () -> Unit navigateToLibrarySettings: (LibraryEvent.OnNavigateToLibrarySettings) -> Unit
) { ) {
LibraryDialog( LibraryDialog(
dialog = dialog, dialog = dialog,
@ -146,6 +146,6 @@ fun LibraryContent(
pagerState = pagerState, pagerState = pagerState,
doublePressExit = doublePressExit, doublePressExit = doublePressExit,
clearSelectedBooks = clearSelectedBooks, clearSelectedBooks = clearSelectedBooks,
searchVisibility = searchVisibility, searchVisibility = searchVisibility
) )
} }

View file

@ -9,7 +9,6 @@ package ua.acclorite.book_story.ui.library
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.library.LibraryEvent import ua.acclorite.book_story.presentation.library.LibraryEvent
@ -21,8 +20,6 @@ fun LibraryDeleteDialog(
actionDeleteDialog: (LibraryEvent.OnActionDeleteDialog) -> Unit, actionDeleteDialog: (LibraryEvent.OnActionDeleteDialog) -> Unit,
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit
) { ) {
val context = LocalContext.current
Dialog( Dialog(
title = stringResource(id = R.string.delete_books), title = stringResource(id = R.string.delete_books),
icon = Icons.Outlined.DeleteOutline, icon = Icons.Outlined.DeleteOutline,
@ -35,11 +32,7 @@ fun LibraryDeleteDialog(
dismissDialog(LibraryEvent.OnDismissDialog) dismissDialog(LibraryEvent.OnDismissDialog)
}, },
onAction = { onAction = {
actionDeleteDialog( actionDeleteDialog(LibraryEvent.OnActionDeleteDialog)
LibraryEvent.OnActionDeleteDialog(
context = context
)
)
}, },
withContent = false withContent = false
) )

View file

@ -22,7 +22,7 @@ fun LibraryDialog(
actionMoveDialog: (LibraryEvent.OnActionMoveDialog) -> Unit, actionMoveDialog: (LibraryEvent.OnActionMoveDialog) -> Unit,
actionDeleteDialog: (LibraryEvent.OnActionDeleteDialog) -> Unit, actionDeleteDialog: (LibraryEvent.OnActionDeleteDialog) -> Unit,
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit, dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit,
navigateToLibrarySettings: () -> Unit navigateToLibrarySettings: (LibraryEvent.OnNavigateToLibrarySettings) -> Unit
) { ) {
when (dialog) { when (dialog) {
LibraryScreen.MOVE_DIALOG -> { LibraryScreen.MOVE_DIALOG -> {

View file

@ -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))
}
}
}
}
}

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
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.library.LibraryEvent
import ua.acclorite.book_story.ui.common.components.placeholder.EmptyPlaceholder import ua.acclorite.book_story.ui.common.components.placeholder.EmptyPlaceholder
import ua.acclorite.book_story.ui.theme.Transitions import ua.acclorite.book_story.ui.theme.Transitions
@ -24,7 +25,7 @@ fun BoxScope.LibraryEmptyPlaceholder(
isLoading: Boolean, isLoading: Boolean,
isRefreshing: Boolean, isRefreshing: Boolean,
isBooksEmpty: Boolean, isBooksEmpty: Boolean,
navigateToBrowse: () -> Unit navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = !isLoading visible = !isLoading
@ -40,7 +41,7 @@ fun BoxScope.LibraryEmptyPlaceholder(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
actionTitle = stringResource(id = R.string.add_book) actionTitle = stringResource(id = R.string.add_book)
) { ) {
navigateToBrowse() navigateToBrowse(LibraryEvent.OnNavigateToBrowse)
} }
} }
} }

View file

@ -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,10 +30,8 @@ fun LibraryMoveDialog(
categories: List<Category>, categories: List<Category>,
actionMoveDialog: (LibraryEvent.OnActionMoveDialog) -> Unit, actionMoveDialog: (LibraryEvent.OnActionMoveDialog) -> Unit,
dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit, dismissDialog: (LibraryEvent.OnDismissDialog) -> Unit,
navigateToLibrarySettings: () -> Unit navigateToLibrarySettings: (LibraryEvent.OnNavigateToLibrarySettings) -> Unit
) { ) {
val context = LocalContext.current
val selectedBooks = remember { val selectedBooks = remember {
derivedStateOf { derivedStateOf {
books.filter { it.selected } books.filter { it.selected }
@ -61,14 +58,13 @@ fun LibraryMoveDialog(
onAction = { onAction = {
actionMoveDialog( actionMoveDialog(
LibraryEvent.OnActionMoveDialog( LibraryEvent.OnActionMoveDialog(
selectedCategories = selectedCategories, selectedCategories = selectedCategories
context = context
) )
) )
}, },
secondaryAction = stringResource(id = R.string.edit), secondaryAction = stringResource(id = R.string.edit),
onSecondaryAction = { onSecondaryAction = {
navigateToLibrarySettings() navigateToLibrarySettings(LibraryEvent.OnNavigateToLibrarySettings)
dismissDialog(LibraryEvent.OnDismissDialog) dismissDialog(LibraryEvent.OnDismissDialog)
}, },
withContent = true, withContent = true,

View file

@ -44,9 +44,9 @@ fun LibraryPager(
isLoading: Boolean, isLoading: Boolean,
isRefreshing: Boolean, isRefreshing: Boolean,
selectBook: (LibraryEvent.OnSelectBook) -> Unit, selectBook: (LibraryEvent.OnSelectBook) -> Unit,
navigateToBrowse: () -> Unit, navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit,
navigateToBookInfo: (id: Int) -> Unit, navigateToBookInfo: (LibraryEvent.OnNavigateToBookInfo) -> Unit,
navigateToReader: (id: Int) -> Unit, navigateToReader: (LibraryEvent.OnNavigateToReader) -> Unit,
) { ) {
val categorizedBooks = remember( val categorizedBooks = remember(
books, books,
@ -172,8 +172,20 @@ fun LibraryPager(
) )
) )
}, },
navigateToBookInfo = { navigateToBookInfo(book.data.id) }, navigateToBookInfo = {
navigateToReader = { navigateToReader(book.data.id) }, navigateToBookInfo(
LibraryEvent.OnNavigateToBookInfo(
book.data.id
)
)
},
navigateToReader = {
navigateToReader(
LibraryEvent.OnNavigateToReader(
book.data.id
)
)
}
) )
} }
} }

View file

@ -63,9 +63,9 @@ fun LibraryScaffold(
showMoveDialog: (LibraryEvent.OnShowMoveDialog) -> Unit, showMoveDialog: (LibraryEvent.OnShowMoveDialog) -> Unit,
showDeleteDialog: (LibraryEvent.OnShowDeleteDialog) -> Unit, showDeleteDialog: (LibraryEvent.OnShowDeleteDialog) -> Unit,
showFilterBottomSheet: (LibraryEvent.OnShowFilterBottomSheet) -> Unit, showFilterBottomSheet: (LibraryEvent.OnShowFilterBottomSheet) -> Unit,
navigateToBrowse: () -> Unit, navigateToBrowse: (LibraryEvent.OnNavigateToBrowse) -> Unit,
navigateToBookInfo: (id: Int) -> Unit, navigateToBookInfo: (LibraryEvent.OnNavigateToBookInfo) -> Unit,
navigateToReader: (id: Int) -> Unit, navigateToReader: (LibraryEvent.OnNavigateToReader) -> Unit,
) { ) {
Scaffold( Scaffold(
Modifier Modifier

View file

@ -189,7 +189,7 @@ fun LibraryTopBar(
modifier = Modifier modifier = Modifier
.focusRequester(focusRequester) .focusRequester(focusRequester)
.onGloballyPositioned { .onGloballyPositioned {
requestFocus(LibraryEvent.OnRequestFocus(focusRequester)) requestFocus(LibraryEvent.OnRequestFocus)
}, },
initialQuery = searchQuery, initialQuery = searchQuery,
onQueryChange = { onQueryChange = {