refactor: history viewmodel
This commit is contained in:
parent
eb87ef8b45
commit
28cbf1188d
13 changed files with 320 additions and 261 deletions
|
|
@ -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.logE
|
||||||
import ua.acclorite.book_story.core.log.logI
|
import ua.acclorite.book_story.core.log.logI
|
||||||
import ua.acclorite.book_story.domain.model.history.History
|
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.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
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetHistoryUseCase @Inject constructor(
|
class GetHistoryUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
private val historyRepository: HistoryRepository
|
private val historyRepository: HistoryRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend operator fun invoke(): List<History> {
|
suspend operator fun invoke(query: String): List<GroupedHistory> {
|
||||||
logI("Getting all history.")
|
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 = {
|
onSuccess = {
|
||||||
logI("Successfully got ${it.size} history entries.")
|
logI("Successfully got ${it.size} grouped history entries.")
|
||||||
it
|
it
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
logE("Could not get history entries with error: ${it.message}")
|
logE("Could not get grouped history entries with error: ${it.message}")
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ object AboutScreen : Screen, Parcelable {
|
||||||
|
|
||||||
val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior()
|
val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior()
|
||||||
|
|
||||||
AboutEffects(screenModel.effects)
|
AboutEffects(
|
||||||
|
effects = screenModel.effects
|
||||||
|
)
|
||||||
|
|
||||||
AboutContent(
|
AboutContent(
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,7 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.presentation.history
|
package ua.acclorite.book_story.presentation.history
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import ua.acclorite.book_story.domain.model.history.History
|
import ua.acclorite.book_story.domain.model.history.History
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -23,9 +20,7 @@ sealed class HistoryEvent {
|
||||||
val show: Boolean
|
val show: Boolean
|
||||||
) : HistoryEvent()
|
) : HistoryEvent()
|
||||||
|
|
||||||
data class OnRequestFocus(
|
data object OnRequestFocus : HistoryEvent()
|
||||||
val focusRequester: FocusRequester
|
|
||||||
) : HistoryEvent()
|
|
||||||
|
|
||||||
data class OnSearchQueryChange(
|
data class OnSearchQueryChange(
|
||||||
val query: String
|
val query: String
|
||||||
|
|
@ -34,16 +29,26 @@ sealed class HistoryEvent {
|
||||||
data object OnSearch : HistoryEvent()
|
data object OnSearch : HistoryEvent()
|
||||||
|
|
||||||
data class OnDeleteHistoryEntry(
|
data class OnDeleteHistoryEntry(
|
||||||
val history: History,
|
val history: History
|
||||||
val snackbarState: SnackbarHostState,
|
) : HistoryEvent()
|
||||||
val context: Context
|
|
||||||
|
data class OnRestoreHistoryEntry(
|
||||||
|
val history: History
|
||||||
) : HistoryEvent()
|
) : HistoryEvent()
|
||||||
|
|
||||||
data object OnShowDeleteWholeHistoryDialog : HistoryEvent()
|
data object OnShowDeleteWholeHistoryDialog : HistoryEvent()
|
||||||
|
|
||||||
data class OnActionDeleteWholeHistoryDialog(
|
data object OnActionDeleteWholeHistoryDialog : HistoryEvent()
|
||||||
val context: Context
|
|
||||||
) : HistoryEvent()
|
|
||||||
|
|
||||||
data object OnDismissDialog : HistoryEvent()
|
data object OnDismissDialog : HistoryEvent()
|
||||||
|
|
||||||
|
data object OnNavigateToLibrary : HistoryEvent()
|
||||||
|
|
||||||
|
data class OnNavigateToBookInfo(
|
||||||
|
val bookId: Int
|
||||||
|
) : HistoryEvent()
|
||||||
|
|
||||||
|
data class OnNavigateToReader(
|
||||||
|
val bookId: Int
|
||||||
|
) : HistoryEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -6,14 +6,16 @@
|
||||||
|
|
||||||
package ua.acclorite.book_story.presentation.history
|
package ua.acclorite.book_story.presentation.history
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarResult
|
|
||||||
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.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
|
||||||
|
|
@ -21,26 +23,18 @@ 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.history.History
|
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.AddHistoryUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.history.DeleteHistoryUseCase
|
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.DeleteWholeHistoryUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.history.GetHistoryUseCase
|
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.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.Date
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class HistoryModel @Inject constructor(
|
class HistoryModel @Inject constructor(
|
||||||
private val getBookUseCase: GetBookUseCase,
|
|
||||||
private val getHistoryUseCase: GetHistoryUseCase,
|
private val getHistoryUseCase: GetHistoryUseCase,
|
||||||
private val addHistoryUseCase: AddHistoryUseCase,
|
private val addHistoryUseCase: AddHistoryUseCase,
|
||||||
private val deleteHistoryUseCase: DeleteHistoryUseCase,
|
private val deleteHistoryUseCase: DeleteHistoryUseCase,
|
||||||
|
|
@ -52,21 +46,21 @@ class HistoryModel @Inject constructor(
|
||||||
private val _state = MutableStateFlow(HistoryState())
|
private val _state = MutableStateFlow(HistoryState())
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private val _effects = MutableSharedFlow<HistoryEffect>()
|
||||||
|
val effects = _effects.asSharedFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
loading = true,
|
loading = true,
|
||||||
hideSearch = true
|
hideSearch = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
/* Observe channel - - - - - - - - - - - */
|
/* Observe channel - - - - - - - - - - - */
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
HistoryScreen.refreshListChannel.receiveAsFlow().collectLatest {
|
HistoryScreen.refreshListChannel.receiveAsFlow().collectLatest { delay ->
|
||||||
delay(it)
|
delay(delay)
|
||||||
yield()
|
|
||||||
|
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
|
|
@ -76,18 +70,17 @@ class HistoryModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
HistoryScreen.insertHistoryChannel.receiveAsFlow().collectLatest {
|
HistoryScreen.insertHistoryChannel.receiveAsFlow().collectLatest { bookId ->
|
||||||
addHistoryUseCase(
|
addHistoryUseCase(
|
||||||
History(
|
History(
|
||||||
bookId = it,
|
bookId = bookId,
|
||||||
book = null,
|
book = null,
|
||||||
time = Date().time
|
time = Date().time
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
delay(500)
|
delay(500)
|
||||||
yield()
|
|
||||||
|
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
|
|
@ -103,13 +96,13 @@ class HistoryModel @Inject constructor(
|
||||||
|
|
||||||
private var refreshJob: Job? = null
|
private var refreshJob: Job? = null
|
||||||
private var searchQueryChange: Job? = null
|
private var searchQueryChange: Job? = null
|
||||||
private var deleteHistoryEntry: Job? = null
|
|
||||||
|
|
||||||
fun onEvent(event: HistoryEvent) {
|
fun onEvent(event: HistoryEvent) {
|
||||||
|
viewModelScope.launch {
|
||||||
when (event) {
|
when (event) {
|
||||||
is HistoryEvent.OnRefreshList -> {
|
is HistoryEvent.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,
|
||||||
|
|
@ -118,21 +111,28 @@ class HistoryModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
yield()
|
ensureActive()
|
||||||
getHistoryFromDatabase()
|
val history = getHistoryUseCase(
|
||||||
|
if (_state.value.showSearch) _state.value.searchQuery
|
||||||
delay(500)
|
else ""
|
||||||
|
)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false,
|
history = history,
|
||||||
isLoading = false
|
isLoading = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delay(500) // Delay for UI smoothness
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isRefreshing = false
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is HistoryEvent.OnSearchVisibility -> {
|
is HistoryEvent.OnSearchVisibility -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
if (!event.show) {
|
if (!event.show) {
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
|
|
@ -140,27 +140,20 @@ class HistoryModel @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 HistoryEvent.OnRequestFocus -> {
|
is HistoryEvent.OnRequestFocus -> {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
|
||||||
if (!_state.value.hasFocused) {
|
if (!_state.value.hasFocused) {
|
||||||
event.focusRequester.requestFocus()
|
_effects.emit(HistoryEffect.OnRequestFocus)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
hasFocused = true
|
hasFocused = true
|
||||||
|
|
@ -168,26 +161,22 @@ class HistoryModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is HistoryEvent.OnSearchQueryChange -> {
|
is HistoryEvent.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(HistoryEvent.OnSearch)
|
onEvent(HistoryEvent.OnSearch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is HistoryEvent.OnSearch -> {
|
is HistoryEvent.OnSearch -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
loading = false,
|
loading = false,
|
||||||
|
|
@ -195,10 +184,9 @@ class HistoryModel @Inject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is HistoryEvent.OnDeleteHistoryEntry -> {
|
is HistoryEvent.OnDeleteHistoryEntry -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(Dispatchers.Default) {
|
||||||
deleteHistoryUseCase(event.history)
|
deleteHistoryUseCase(event.history)
|
||||||
|
|
||||||
onEvent(
|
onEvent(
|
||||||
|
|
@ -209,28 +197,13 @@ class HistoryModel @Inject constructor(
|
||||||
)
|
)
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
deleteHistoryEntry?.cancel()
|
_effects.emit(HistoryEffect.OnShowSnackbar(event.history))
|
||||||
event.snackbarState.currentSnackbarData?.dismiss()
|
}
|
||||||
|
|
||||||
deleteHistoryEntry = launch(Dispatchers.IO) {
|
|
||||||
repeat(10) {
|
|
||||||
yield()
|
|
||||||
delay(1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield()
|
is HistoryEvent.OnRestoreHistoryEntry -> {
|
||||||
event.snackbarState.currentSnackbarData?.dismiss()
|
withContext(Dispatchers.Default) {
|
||||||
}
|
|
||||||
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 -> {
|
|
||||||
addHistoryUseCase(event.history)
|
addHistoryUseCase(event.history)
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
|
||||||
|
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
|
|
@ -238,23 +211,20 @@ class HistoryModel @Inject constructor(
|
||||||
hideSearch = false
|
hideSearch = false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is HistoryEvent.OnShowDeleteWholeHistoryDialog -> {
|
is HistoryEvent.OnShowDeleteWholeHistoryDialog -> {
|
||||||
viewModelScope.launch {
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialog = HistoryScreen.DELETE_WHOLE_HISTORY_DIALOG
|
dialog = HistoryScreen.DELETE_WHOLE_HISTORY_DIALOG
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is HistoryEvent.OnActionDeleteWholeHistoryDialog -> {
|
is HistoryEvent.OnActionDeleteWholeHistoryDialog -> {
|
||||||
viewModelScope.launch {
|
withContext(Dispatchers.Default) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialog = null,
|
dialog = null,
|
||||||
|
|
@ -263,105 +233,45 @@ class HistoryModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteWholeHistoryUseCase()
|
deleteWholeHistoryUseCase()
|
||||||
LibraryScreen.refreshListChannel.trySend(0)
|
|
||||||
onEvent(
|
onEvent(
|
||||||
HistoryEvent.OnRefreshList(
|
HistoryEvent.OnRefreshList(
|
||||||
loading = true,
|
loading = true,
|
||||||
hideSearch = true
|
hideSearch = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
LibraryScreen.refreshListChannel.trySend(0)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
_effects.emit(HistoryEffect.OnWholeHistoryDeleted)
|
||||||
event.context
|
|
||||||
.getString(R.string.history_deleted)
|
|
||||||
.showToast(context = event.context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is HistoryEvent.OnDismissDialog -> {
|
is HistoryEvent.OnDismissDialog -> {
|
||||||
viewModelScope.launch {
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialog = null
|
dialog = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
is HistoryEvent.OnNavigateToLibrary -> {
|
||||||
|
_effects.emit(HistoryEffect.OnNavigateToLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getHistoryFromDatabase(
|
is HistoryEvent.OnNavigateToBookInfo -> {
|
||||||
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
|
_effects.emit(HistoryEffect.OnNavigateToBookInfo(event.bookId))
|
||||||
) {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterMaxElementsById(elements: List<History>): List<History> {
|
is HistoryEvent.OnNavigateToReader -> {
|
||||||
val groupedById = elements.groupBy { it.bookId }
|
_effects.emit(HistoryEffect.OnNavigateToReader(event.bookId))
|
||||||
val maxElementsById = groupedById.map { (_, values) ->
|
|
||||||
values.maxByOrNull { it.time }
|
|
||||||
}
|
}
|
||||||
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) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
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.library.LibraryScreen
|
|
||||||
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.ui.history.HistoryContent
|
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
|
@Parcelize
|
||||||
object HistoryScreen : Screen, Parcelable {
|
object HistoryScreen : Screen, Parcelable {
|
||||||
|
|
@ -49,7 +46,6 @@ object HistoryScreen : Screen, Parcelable {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.current
|
|
||||||
val screenModel = hiltViewModel<HistoryModel>()
|
val screenModel = hiltViewModel<HistoryModel>()
|
||||||
|
|
||||||
val state = screenModel.state.collectAsStateWithLifecycle()
|
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(
|
HistoryContent(
|
||||||
refreshState = refreshState,
|
refreshState = refreshState,
|
||||||
snackbarState = snackbarState,
|
snackbarState = snackbarState,
|
||||||
|
|
@ -100,16 +103,9 @@ object HistoryScreen : Screen, Parcelable {
|
||||||
showDeleteWholeHistoryDialog = screenModel::onEvent,
|
showDeleteWholeHistoryDialog = screenModel::onEvent,
|
||||||
actionDeleteWholeHistoryDialog = screenModel::onEvent,
|
actionDeleteWholeHistoryDialog = screenModel::onEvent,
|
||||||
dismissDialog = screenModel::onEvent,
|
dismissDialog = screenModel::onEvent,
|
||||||
navigateToLibrary = {
|
navigateToLibrary = screenModel::onEvent,
|
||||||
navigator.push(LibraryScreen, saveInBackStack = false)
|
navigateToBookInfo = screenModel::onEvent,
|
||||||
},
|
navigateToReader = screenModel::onEvent
|
||||||
navigateToBookInfo = {
|
|
||||||
navigator.push(BookInfoScreen(bookId = it))
|
|
||||||
},
|
|
||||||
navigateToReader = {
|
|
||||||
insertHistoryChannel.trySend(it)
|
|
||||||
navigator.push(ReaderScreen(it))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ import ua.acclorite.book_story.presentation.history.HistoryEvent
|
||||||
fun HistoryBackHandler(
|
fun HistoryBackHandler(
|
||||||
showSearch: Boolean,
|
showSearch: Boolean,
|
||||||
searchVisibility: (HistoryEvent.OnSearchVisibility) -> Unit,
|
searchVisibility: (HistoryEvent.OnSearchVisibility) -> Unit,
|
||||||
navigateToLibrary: () -> Unit
|
navigateToLibrary: (HistoryEvent.OnNavigateToLibrary) -> Unit
|
||||||
) {
|
) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
if (showSearch) {
|
if (showSearch) {
|
||||||
|
|
@ -22,6 +22,6 @@ fun HistoryBackHandler(
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToLibrary()
|
navigateToLibrary(HistoryEvent.OnNavigateToLibrary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,9 +39,9 @@ fun HistoryContent(
|
||||||
showDeleteWholeHistoryDialog: (HistoryEvent.OnShowDeleteWholeHistoryDialog) -> Unit,
|
showDeleteWholeHistoryDialog: (HistoryEvent.OnShowDeleteWholeHistoryDialog) -> Unit,
|
||||||
actionDeleteWholeHistoryDialog: (HistoryEvent.OnActionDeleteWholeHistoryDialog) -> Unit,
|
actionDeleteWholeHistoryDialog: (HistoryEvent.OnActionDeleteWholeHistoryDialog) -> Unit,
|
||||||
dismissDialog: (HistoryEvent.OnDismissDialog) -> Unit,
|
dismissDialog: (HistoryEvent.OnDismissDialog) -> Unit,
|
||||||
navigateToLibrary: () -> Unit,
|
navigateToLibrary: (HistoryEvent.OnNavigateToLibrary) -> Unit,
|
||||||
navigateToBookInfo: (Int) -> Unit,
|
navigateToBookInfo: (HistoryEvent.OnNavigateToBookInfo) -> Unit,
|
||||||
navigateToReader: (Int) -> Unit
|
navigateToReader: (HistoryEvent.OnNavigateToReader) -> Unit
|
||||||
) {
|
) {
|
||||||
HistoryDialog(
|
HistoryDialog(
|
||||||
dialog = dialog,
|
dialog = dialog,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package ua.acclorite.book_story.ui.history
|
||||||
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.history.HistoryEvent
|
import ua.acclorite.book_story.presentation.history.HistoryEvent
|
||||||
|
|
@ -20,7 +19,6 @@ fun HistoryDeleteWholeHistoryDialog(
|
||||||
actionDeleteWholeHistoryDialog: (HistoryEvent.OnActionDeleteWholeHistoryDialog) -> Unit,
|
actionDeleteWholeHistoryDialog: (HistoryEvent.OnActionDeleteWholeHistoryDialog) -> Unit,
|
||||||
dismissDialog: (HistoryEvent.OnDismissDialog) -> Unit
|
dismissDialog: (HistoryEvent.OnDismissDialog) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
Dialog(
|
Dialog(
|
||||||
title = stringResource(id = R.string.delete_history),
|
title = stringResource(id = R.string.delete_history),
|
||||||
icon = Icons.Outlined.DeleteOutline,
|
icon = Icons.Outlined.DeleteOutline,
|
||||||
|
|
@ -30,11 +28,7 @@ fun HistoryDeleteWholeHistoryDialog(
|
||||||
dismissDialog(HistoryEvent.OnDismissDialog)
|
dismissDialog(HistoryEvent.OnDismissDialog)
|
||||||
},
|
},
|
||||||
onAction = {
|
onAction = {
|
||||||
actionDeleteWholeHistoryDialog(
|
actionDeleteWholeHistoryDialog(HistoryEvent.OnActionDeleteWholeHistoryDialog)
|
||||||
HistoryEvent.OnActionDeleteWholeHistoryDialog(
|
|
||||||
context = context
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
withContent = false
|
withContent = false
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
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.presentation.history.model.GroupedHistory
|
||||||
import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar
|
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.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.settings.components.SettingsSubcategoryTitle
|
||||||
import ua.acclorite.book_story.ui.theme.DefaultTransition
|
import ua.acclorite.book_story.ui.theme.DefaultTransition
|
||||||
|
|
||||||
|
|
@ -29,15 +27,12 @@ import ua.acclorite.book_story.ui.theme.DefaultTransition
|
||||||
fun HistoryLayout(
|
fun HistoryLayout(
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
history: List<GroupedHistory>,
|
history: List<GroupedHistory>,
|
||||||
snackbarState: SnackbarHostState,
|
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
isRefreshing: Boolean,
|
isRefreshing: Boolean,
|
||||||
deleteHistoryEntry: (HistoryEvent.OnDeleteHistoryEntry) -> Unit,
|
deleteHistoryEntry: (HistoryEvent.OnDeleteHistoryEntry) -> Unit,
|
||||||
navigateToBookInfo: (Int) -> Unit,
|
navigateToBookInfo: (HistoryEvent.OnNavigateToBookInfo) -> Unit,
|
||||||
navigateToReader: (Int) -> Unit,
|
navigateToReader: (HistoryEvent.OnNavigateToReader) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalActivity.current
|
|
||||||
|
|
||||||
DefaultTransition(visible = !isLoading) {
|
DefaultTransition(visible = !isLoading) {
|
||||||
LazyColumnWithScrollbar(
|
LazyColumnWithScrollbar(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
@ -75,17 +70,23 @@ fun HistoryLayout(
|
||||||
historyEntry = historyEntry,
|
historyEntry = historyEntry,
|
||||||
isRefreshing = isRefreshing,
|
isRefreshing = isRefreshing,
|
||||||
onBodyClick = {
|
onBodyClick = {
|
||||||
navigateToBookInfo(historyEntry.bookId)
|
navigateToBookInfo(
|
||||||
|
HistoryEvent.OnNavigateToBookInfo(
|
||||||
|
historyEntry.bookId
|
||||||
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onTitleClick = {
|
onTitleClick = {
|
||||||
navigateToReader(historyEntry.bookId)
|
navigateToReader(
|
||||||
|
HistoryEvent.OnNavigateToReader(
|
||||||
|
historyEntry.bookId
|
||||||
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onDeleteClick = {
|
onDeleteClick = {
|
||||||
deleteHistoryEntry(
|
deleteHistoryEntry(
|
||||||
HistoryEvent.OnDeleteHistoryEntry(
|
HistoryEvent.OnDeleteHistoryEntry(
|
||||||
history = historyEntry,
|
history = historyEntry
|
||||||
snackbarState = snackbarState,
|
|
||||||
context = context,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ fun HistoryScaffold(
|
||||||
deleteHistoryEntry: (HistoryEvent.OnDeleteHistoryEntry) -> Unit,
|
deleteHistoryEntry: (HistoryEvent.OnDeleteHistoryEntry) -> Unit,
|
||||||
showDeleteWholeHistoryDialog: (HistoryEvent.OnShowDeleteWholeHistoryDialog) -> Unit,
|
showDeleteWholeHistoryDialog: (HistoryEvent.OnShowDeleteWholeHistoryDialog) -> Unit,
|
||||||
search: (HistoryEvent.OnSearch) -> Unit,
|
search: (HistoryEvent.OnSearch) -> Unit,
|
||||||
navigateToBookInfo: (Int) -> Unit,
|
navigateToBookInfo: (HistoryEvent.OnNavigateToBookInfo) -> Unit,
|
||||||
navigateToReader: (Int) -> Unit
|
navigateToReader: (HistoryEvent.OnNavigateToReader) -> Unit
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -79,7 +79,6 @@ fun HistoryScaffold(
|
||||||
HistoryLayout(
|
HistoryLayout(
|
||||||
listState = listState,
|
listState = listState,
|
||||||
history = history,
|
history = history,
|
||||||
snackbarState = snackbarState,
|
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
isRefreshing = isRefreshing,
|
isRefreshing = isRefreshing,
|
||||||
deleteHistoryEntry = deleteHistoryEntry,
|
deleteHistoryEntry = deleteHistoryEntry,
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ fun HistoryTopBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
requestFocus(HistoryEvent.OnRequestFocus(focusRequester))
|
requestFocus(HistoryEvent.OnRequestFocus)
|
||||||
},
|
},
|
||||||
initialQuery = searchQuery,
|
initialQuery = searchQuery,
|
||||||
onQueryChange = {
|
onQueryChange = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue