refactor: history viewmodel

This commit is contained in:
Acclorite 2025-08-09 10:11:47 +03:00
parent eb87ef8b45
commit 28cbf1188d
No known key found for this signature in database
GPG key ID: 6E54C611F6EE8593
13 changed files with 320 additions and 261 deletions

View file

@ -9,23 +9,66 @@ package ua.acclorite.book_story.domain.use_case.history
import ua.acclorite.book_story.core.log.logE
import ua.acclorite.book_story.core.log.logI
import ua.acclorite.book_story.domain.model.history.History
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.domain.repository.HistoryRepository
import ua.acclorite.book_story.presentation.history.model.GroupedHistory
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import javax.inject.Inject
class GetHistoryUseCase @Inject constructor(
private val bookRepository: BookRepository,
private val historyRepository: HistoryRepository
) {
suspend operator fun invoke(): List<History> {
suspend operator fun invoke(query: String): List<GroupedHistory> {
logI("Getting all history.")
return historyRepository.getHistory().fold(
fun getDayLabel(timeMillis: Long): String {
val historyDate = Instant.ofEpochMilli(timeMillis)
.atZone(ZoneId.systemDefault()).toLocalDate()
val today = LocalDate.now()
val yesterday = today.minusDays(1)
return when (historyDate) {
today -> "today"
yesterday -> "yesterday"
else -> historyDate.format(DateTimeFormatter.ofPattern("dd.MM.yy"))
}
}
fun filterMaxElementsById(elements: List<History>): List<History> {
val groupedById = elements.groupBy { it.bookId }
val maxElementsById = groupedById.map { (_, values) ->
values.maxByOrNull { it.time }
}
return maxElementsById.filterNotNull()
}
return runCatching {
historyRepository.getHistory().getOrThrow().sortedByDescending { it.time }
.mapNotNull { history ->
val book = bookRepository.getBook(history.bookId).getOrNull()
if (
book == null
|| !book.title.lowercase().trim().contains(query.lowercase().trim())
) return@mapNotNull null
history.copy(book = book)
}
.groupBy { history ->
getDayLabel(history.time)
}
.map { (day, history) -> GroupedHistory(day, filterMaxElementsById(history)) }
}.fold(
onSuccess = {
logI("Successfully got ${it.size} history entries.")
logI("Successfully got ${it.size} grouped history entries.")
it
},
onFailure = {
logE("Could not get history entries with error: ${it.message}")
logE("Could not get grouped history entries with error: ${it.message}")
emptyList()
}
)

View file

@ -27,7 +27,9 @@ object AboutScreen : Screen, Parcelable {
val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior()
AboutEffects(screenModel.effects)
AboutEffects(
effects = screenModel.effects
)
AboutContent(
scrollBehavior = scrollBehavior,

View file

@ -0,0 +1,31 @@
/*
* 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.history
import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.history.History
@Immutable
sealed class HistoryEffect {
data object OnRequestFocus : HistoryEffect()
data class OnShowSnackbar(
val history: History
) : HistoryEffect()
data object OnWholeHistoryDeleted : HistoryEffect()
data object OnNavigateToLibrary : HistoryEffect()
data class OnNavigateToBookInfo(
val bookId: Int
) : HistoryEffect()
data class OnNavigateToReader(
val bookId: Int
) : HistoryEffect()
}

View file

@ -6,10 +6,7 @@
package ua.acclorite.book_story.presentation.history
import android.content.Context
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Immutable
import androidx.compose.ui.focus.FocusRequester
import ua.acclorite.book_story.domain.model.history.History
@Immutable
@ -23,9 +20,7 @@ sealed class HistoryEvent {
val show: Boolean
) : HistoryEvent()
data class OnRequestFocus(
val focusRequester: FocusRequester
) : HistoryEvent()
data object OnRequestFocus : HistoryEvent()
data class OnSearchQueryChange(
val query: String
@ -34,16 +29,26 @@ sealed class HistoryEvent {
data object OnSearch : HistoryEvent()
data class OnDeleteHistoryEntry(
val history: History,
val snackbarState: SnackbarHostState,
val context: Context
val history: History
) : HistoryEvent()
data class OnRestoreHistoryEntry(
val history: History
) : HistoryEvent()
data object OnShowDeleteWholeHistoryDialog : HistoryEvent()
data class OnActionDeleteWholeHistoryDialog(
val context: Context
) : HistoryEvent()
data object OnActionDeleteWholeHistoryDialog : HistoryEvent()
data object OnDismissDialog : HistoryEvent()
data object OnNavigateToLibrary : HistoryEvent()
data class OnNavigateToBookInfo(
val bookId: Int
) : HistoryEvent()
data class OnNavigateToReader(
val bookId: Int
) : HistoryEvent()
}

View file

@ -6,14 +6,16 @@
package ua.acclorite.book_story.presentation.history
import androidx.compose.material3.SnackbarResult
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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
@ -21,26 +23,18 @@ 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.history.History
import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase
import ua.acclorite.book_story.domain.use_case.history.AddHistoryUseCase
import ua.acclorite.book_story.domain.use_case.history.DeleteHistoryUseCase
import ua.acclorite.book_story.domain.use_case.history.DeleteWholeHistoryUseCase
import ua.acclorite.book_story.domain.use_case.history.GetHistoryUseCase
import ua.acclorite.book_story.presentation.history.model.GroupedHistory
import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.ui.common.helpers.showToast
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
@HiltViewModel
class HistoryModel @Inject constructor(
private val getBookUseCase: GetBookUseCase,
private val getHistoryUseCase: GetHistoryUseCase,
private val addHistoryUseCase: AddHistoryUseCase,
private val deleteHistoryUseCase: DeleteHistoryUseCase,
@ -52,21 +46,21 @@ class HistoryModel @Inject constructor(
private val _state = MutableStateFlow(HistoryState())
val state = _state.asStateFlow()
private val _effects = MutableSharedFlow<HistoryEffect>()
val effects = _effects.asSharedFlow()
init {
viewModelScope.launch(Dispatchers.IO) {
onEvent(
HistoryEvent.OnRefreshList(
loading = true,
hideSearch = true
)
)
}
/* Observe channel - - - - - - - - - - - */
viewModelScope.launch(Dispatchers.IO) {
HistoryScreen.refreshListChannel.receiveAsFlow().collectLatest {
delay(it)
yield()
viewModelScope.launch {
HistoryScreen.refreshListChannel.receiveAsFlow().collectLatest { delay ->
delay(delay)
onEvent(
HistoryEvent.OnRefreshList(
@ -76,18 +70,17 @@ class HistoryModel @Inject constructor(
)
}
}
viewModelScope.launch(Dispatchers.IO) {
HistoryScreen.insertHistoryChannel.receiveAsFlow().collectLatest {
viewModelScope.launch {
HistoryScreen.insertHistoryChannel.receiveAsFlow().collectLatest { bookId ->
addHistoryUseCase(
History(
bookId = it,
bookId = bookId,
book = null,
time = Date().time
)
)
delay(500)
yield()
onEvent(
HistoryEvent.OnRefreshList(
@ -103,13 +96,13 @@ class HistoryModel @Inject constructor(
private var refreshJob: Job? = null
private var searchQueryChange: Job? = null
private var deleteHistoryEntry: Job? = null
fun onEvent(event: HistoryEvent) {
viewModelScope.launch {
when (event) {
is HistoryEvent.OnRefreshList -> {
refreshJob?.cancel()
refreshJob = viewModelScope.launch(Dispatchers.IO) {
refreshJob = viewModelScope.launch(Dispatchers.Default) {
_state.update {
it.copy(
isRefreshing = true,
@ -118,21 +111,28 @@ class HistoryModel @Inject constructor(
)
}
yield()
getHistoryFromDatabase()
delay(500)
ensureActive()
val history = getHistoryUseCase(
if (_state.value.showSearch) _state.value.searchQuery
else ""
)
_state.update {
it.copy(
isRefreshing = false,
history = history,
isLoading = false
)
}
delay(500) // Delay for UI smoothness
_state.update {
it.copy(
isRefreshing = false
)
}
}
}
is HistoryEvent.OnSearchVisibility -> {
viewModelScope.launch(Dispatchers.IO) {
if (!event.show) {
onEvent(
HistoryEvent.OnRefreshList(
@ -140,27 +140,20 @@ class HistoryModel @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 HistoryEvent.OnRequestFocus -> {
viewModelScope.launch(Dispatchers.Main) {
if (!_state.value.hasFocused) {
event.focusRequester.requestFocus()
_effects.emit(HistoryEffect.OnRequestFocus)
_state.update {
it.copy(
hasFocused = true
@ -168,26 +161,22 @@ class HistoryModel @Inject constructor(
}
}
}
}
is HistoryEvent.OnSearchQueryChange -> {
viewModelScope.launch {
_state.update {
it.copy(
searchQuery = event.query
)
}
searchQueryChange?.cancel()
searchQueryChange = launch(Dispatchers.IO) {
searchQueryChange = viewModelScope.launch(Dispatchers.IO) {
delay(500)
yield()
onEvent(HistoryEvent.OnSearch)
}
}
}
is HistoryEvent.OnSearch -> {
viewModelScope.launch(Dispatchers.IO) {
onEvent(
HistoryEvent.OnRefreshList(
loading = false,
@ -195,10 +184,9 @@ class HistoryModel @Inject constructor(
)
)
}
}
is HistoryEvent.OnDeleteHistoryEntry -> {
viewModelScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Default) {
deleteHistoryUseCase(event.history)
onEvent(
@ -209,28 +197,13 @@ class HistoryModel @Inject constructor(
)
LibraryScreen.refreshListChannel.trySend(0)
deleteHistoryEntry?.cancel()
event.snackbarState.currentSnackbarData?.dismiss()
deleteHistoryEntry = launch(Dispatchers.IO) {
repeat(10) {
yield()
delay(1000)
_effects.emit(HistoryEffect.OnShowSnackbar(event.history))
}
}
yield()
event.snackbarState.currentSnackbarData?.dismiss()
}
val snackbarResult = event.snackbarState.showSnackbar(
event.context.getString(R.string.history_element_deleted),
event.context.getString(R.string.undo)
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Unit
SnackbarResult.ActionPerformed -> {
is HistoryEvent.OnRestoreHistoryEntry -> {
withContext(Dispatchers.Default) {
addHistoryUseCase(event.history)
LibraryScreen.refreshListChannel.trySend(0)
onEvent(
HistoryEvent.OnRefreshList(
@ -238,23 +211,20 @@ class HistoryModel @Inject constructor(
hideSearch = false
)
)
}
}
LibraryScreen.refreshListChannel.trySend(0)
}
}
is HistoryEvent.OnShowDeleteWholeHistoryDialog -> {
viewModelScope.launch {
_state.update {
it.copy(
dialog = HistoryScreen.DELETE_WHOLE_HISTORY_DIALOG
)
}
}
}
is HistoryEvent.OnActionDeleteWholeHistoryDialog -> {
viewModelScope.launch {
withContext(Dispatchers.Default) {
_state.update {
it.copy(
dialog = null,
@ -263,105 +233,45 @@ class HistoryModel @Inject constructor(
}
deleteWholeHistoryUseCase()
LibraryScreen.refreshListChannel.trySend(0)
onEvent(
HistoryEvent.OnRefreshList(
loading = true,
hideSearch = true
)
)
LibraryScreen.refreshListChannel.trySend(0)
withContext(Dispatchers.Main) {
event.context
.getString(R.string.history_deleted)
.showToast(context = event.context)
}
_effects.emit(HistoryEffect.OnWholeHistoryDeleted)
}
}
is HistoryEvent.OnDismissDialog -> {
viewModelScope.launch {
_state.update {
it.copy(
dialog = null
)
}
}
}
}
is HistoryEvent.OnNavigateToLibrary -> {
_effects.emit(HistoryEffect.OnNavigateToLibrary)
}
private suspend fun getHistoryFromDatabase(
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
) {
fun isSameDay(historyTime: Calendar, nowTime: Calendar): Boolean {
return historyTime.get(Calendar.YEAR) == nowTime.get(Calendar.YEAR) &&
historyTime.get(Calendar.DAY_OF_YEAR) == nowTime.get(Calendar.DAY_OF_YEAR)
is HistoryEvent.OnNavigateToBookInfo -> {
_effects.emit(HistoryEffect.OnNavigateToBookInfo(event.bookId))
}
fun filterMaxElementsById(elements: List<History>): List<History> {
val groupedById = elements.groupBy { it.bookId }
val maxElementsById = groupedById.map { (_, values) ->
values.maxByOrNull { it.time }
is HistoryEvent.OnNavigateToReader -> {
_effects.emit(HistoryEffect.OnNavigateToReader(event.bookId))
}
return maxElementsById.filterNotNull()
}
val history = getHistoryUseCase().sortedByDescending {
it.time
}.run {
val books = map { it.bookId }.distinct().mapNotNull {
getBookUseCase(it)
}.toMutableList()
mapNotNull {
val book = books.find { book -> book.id == it.bookId } ?: return@mapNotNull null
if (!book.title.lowercase().trim().contains(query.lowercase().trim())) {
return@mapNotNull null
}
it.copy(book = book)
}
}.ifEmpty {
_state.update {
it.copy(
history = emptyList(),
isLoading = false
)
}
return
}.groupBy { item ->
val historyTime = Calendar.getInstance().apply {
timeInMillis = item.time
}
val nowTime = Calendar.getInstance()
return@groupBy when {
isSameDay(historyTime, nowTime) -> "today"
isSameDay(
historyTime,
nowTime.apply { add(Calendar.DAY_OF_YEAR, -1) }
) -> "yesterday"
else -> SimpleDateFormat(
"dd.MM.yy",
Locale.getDefault()
).format(item.time)
}
}.map { (day, history) -> GroupedHistory(day, filterMaxElementsById(history)) }
_state.update {
it.copy(
history = history,
isLoading = false,
)
}
}
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
mutex.withLock {
yield()
coroutineContext.ensureActive()
this.value = function(this.value)
}
}

View file

@ -22,12 +22,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.channels.Channel
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.reader.ReaderScreen
import ua.acclorite.book_story.ui.history.HistoryContent
import ua.acclorite.book_story.ui.navigator.LocalNavigator
import ua.acclorite.book_story.ui.history.HistoryEffects
@Parcelize
object HistoryScreen : Screen, Parcelable {
@ -49,7 +46,6 @@ object HistoryScreen : Screen, Parcelable {
@Composable
override fun Content() {
val navigator = LocalNavigator.current
val screenModel = hiltViewModel<HistoryModel>()
val state = screenModel.state.collectAsStateWithLifecycle()
@ -79,6 +75,13 @@ object HistoryScreen : Screen, Parcelable {
}
)
HistoryEffects(
effects = screenModel.effects,
focusRequester = focusRequester,
snackbarState = snackbarState,
restoreHistoryEntry = screenModel::onEvent
)
HistoryContent(
refreshState = refreshState,
snackbarState = snackbarState,
@ -100,16 +103,9 @@ object HistoryScreen : Screen, Parcelable {
showDeleteWholeHistoryDialog = screenModel::onEvent,
actionDeleteWholeHistoryDialog = screenModel::onEvent,
dismissDialog = screenModel::onEvent,
navigateToLibrary = {
navigator.push(LibraryScreen, saveInBackStack = false)
},
navigateToBookInfo = {
navigator.push(BookInfoScreen(bookId = it))
},
navigateToReader = {
insertHistoryChannel.trySend(it)
navigator.push(ReaderScreen(it))
}
navigateToLibrary = screenModel::onEvent,
navigateToBookInfo = screenModel::onEvent,
navigateToReader = screenModel::onEvent
)
}
}

View file

@ -14,7 +14,7 @@ import ua.acclorite.book_story.presentation.history.HistoryEvent
fun HistoryBackHandler(
showSearch: Boolean,
searchVisibility: (HistoryEvent.OnSearchVisibility) -> Unit,
navigateToLibrary: () -> Unit
navigateToLibrary: (HistoryEvent.OnNavigateToLibrary) -> Unit
) {
BackHandler {
if (showSearch) {
@ -22,6 +22,6 @@ fun HistoryBackHandler(
return@BackHandler
}
navigateToLibrary()
navigateToLibrary(HistoryEvent.OnNavigateToLibrary)
}
}

View file

@ -39,9 +39,9 @@ fun HistoryContent(
showDeleteWholeHistoryDialog: (HistoryEvent.OnShowDeleteWholeHistoryDialog) -> Unit,
actionDeleteWholeHistoryDialog: (HistoryEvent.OnActionDeleteWholeHistoryDialog) -> Unit,
dismissDialog: (HistoryEvent.OnDismissDialog) -> Unit,
navigateToLibrary: () -> Unit,
navigateToBookInfo: (Int) -> Unit,
navigateToReader: (Int) -> Unit
navigateToLibrary: (HistoryEvent.OnNavigateToLibrary) -> Unit,
navigateToBookInfo: (HistoryEvent.OnNavigateToBookInfo) -> Unit,
navigateToReader: (HistoryEvent.OnNavigateToReader) -> Unit
) {
HistoryDialog(
dialog = dialog,

View file

@ -9,7 +9,6 @@ package ua.acclorite.book_story.ui.history
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.history.HistoryEvent
@ -20,7 +19,6 @@ fun HistoryDeleteWholeHistoryDialog(
actionDeleteWholeHistoryDialog: (HistoryEvent.OnActionDeleteWholeHistoryDialog) -> Unit,
dismissDialog: (HistoryEvent.OnDismissDialog) -> Unit
) {
val context = LocalContext.current
Dialog(
title = stringResource(id = R.string.delete_history),
icon = Icons.Outlined.DeleteOutline,
@ -30,11 +28,7 @@ fun HistoryDeleteWholeHistoryDialog(
dismissDialog(HistoryEvent.OnDismissDialog)
},
onAction = {
actionDeleteWholeHistoryDialog(
HistoryEvent.OnActionDeleteWholeHistoryDialog(
context = context
)
)
actionDeleteWholeHistoryDialog(HistoryEvent.OnActionDeleteWholeHistoryDialog)
},
withContent = false
)

View file

@ -0,0 +1,78 @@
/*
* 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.history
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
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.history.HistoryEffect
import ua.acclorite.book_story.presentation.history.HistoryEvent
import ua.acclorite.book_story.presentation.history.HistoryScreen.insertHistoryChannel
import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.presentation.reader.ReaderScreen
import ua.acclorite.book_story.ui.common.helpers.showToast
import ua.acclorite.book_story.ui.navigator.LocalNavigator
@Composable
fun HistoryEffects(
effects: SharedFlow<HistoryEffect>,
focusRequester: FocusRequester,
snackbarState: SnackbarHostState,
restoreHistoryEntry: (HistoryEvent.OnRestoreHistoryEntry) -> Unit
) {
val navigator = LocalNavigator.current
val context = LocalContext.current
LaunchedEffect(Unit) {
effects.collect { effect ->
when (effect) {
is HistoryEffect.OnRequestFocus -> {
focusRequester.requestFocus()
}
is HistoryEffect.OnShowSnackbar -> {
val snackbarResult = snackbarState.showSnackbar(
context.getString(R.string.history_element_deleted),
context.getString(R.string.undo)
)
if (snackbarResult == SnackbarResult.ActionPerformed) {
restoreHistoryEntry(
HistoryEvent.OnRestoreHistoryEntry(
history = effect.history
)
)
}
}
is HistoryEffect.OnWholeHistoryDeleted -> {
context.getString(R.string.history_deleted)
.showToast(context = context)
}
is HistoryEffect.OnNavigateToLibrary -> {
navigator.push(LibraryScreen, saveInBackStack = false)
}
is HistoryEffect.OnNavigateToBookInfo -> {
navigator.push(BookInfoScreen(bookId = effect.bookId))
}
is HistoryEffect.OnNavigateToReader -> {
insertHistoryChannel.trySend(effect.bookId)
navigator.push(ReaderScreen(bookId = effect.bookId))
}
}
}
}
}

View file

@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@ -21,7 +20,6 @@ import ua.acclorite.book_story.presentation.history.HistoryEvent
import ua.acclorite.book_story.presentation.history.model.GroupedHistory
import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar
import ua.acclorite.book_story.ui.common.data.ScrollbarData
import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.settings.components.SettingsSubcategoryTitle
import ua.acclorite.book_story.ui.theme.DefaultTransition
@ -29,15 +27,12 @@ import ua.acclorite.book_story.ui.theme.DefaultTransition
fun HistoryLayout(
listState: LazyListState,
history: List<GroupedHistory>,
snackbarState: SnackbarHostState,
isLoading: Boolean,
isRefreshing: Boolean,
deleteHistoryEntry: (HistoryEvent.OnDeleteHistoryEntry) -> Unit,
navigateToBookInfo: (Int) -> Unit,
navigateToReader: (Int) -> Unit,
navigateToBookInfo: (HistoryEvent.OnNavigateToBookInfo) -> Unit,
navigateToReader: (HistoryEvent.OnNavigateToReader) -> Unit,
) {
val context = LocalActivity.current
DefaultTransition(visible = !isLoading) {
LazyColumnWithScrollbar(
modifier = Modifier.fillMaxSize(),
@ -75,17 +70,23 @@ fun HistoryLayout(
historyEntry = historyEntry,
isRefreshing = isRefreshing,
onBodyClick = {
navigateToBookInfo(historyEntry.bookId)
navigateToBookInfo(
HistoryEvent.OnNavigateToBookInfo(
historyEntry.bookId
)
)
},
onTitleClick = {
navigateToReader(historyEntry.bookId)
navigateToReader(
HistoryEvent.OnNavigateToReader(
historyEntry.bookId
)
)
},
onDeleteClick = {
deleteHistoryEntry(
HistoryEvent.OnDeleteHistoryEntry(
history = historyEntry,
snackbarState = snackbarState,
context = context,
history = historyEntry
)
)
}

View file

@ -43,8 +43,8 @@ fun HistoryScaffold(
deleteHistoryEntry: (HistoryEvent.OnDeleteHistoryEntry) -> Unit,
showDeleteWholeHistoryDialog: (HistoryEvent.OnShowDeleteWholeHistoryDialog) -> Unit,
search: (HistoryEvent.OnSearch) -> Unit,
navigateToBookInfo: (Int) -> Unit,
navigateToReader: (Int) -> Unit
navigateToBookInfo: (HistoryEvent.OnNavigateToBookInfo) -> Unit,
navigateToReader: (HistoryEvent.OnNavigateToReader) -> Unit
) {
Scaffold(
Modifier
@ -79,7 +79,6 @@ fun HistoryScaffold(
HistoryLayout(
listState = listState,
history = history,
snackbarState = snackbarState,
isLoading = isLoading,
isRefreshing = isRefreshing,
deleteHistoryEntry = deleteHistoryEntry,

View file

@ -98,7 +98,7 @@ fun HistoryTopBar(
modifier = Modifier
.focusRequester(focusRequester)
.onGloballyPositioned {
requestFocus(HistoryEvent.OnRequestFocus(focusRequester))
requestFocus(HistoryEvent.OnRequestFocus)
},
initialQuery = searchQuery,
onQueryChange = {