refactor: browse viewmodel
This commit is contained in:
parent
6f53c70b5b
commit
d50b1a5f9a
14 changed files with 398 additions and 414 deletions
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.domain.use_case.file_system
|
||||
|
||||
import ua.acclorite.book_story.core.helpers.compareByWithOrder
|
||||
import ua.acclorite.book_story.core.log.logE
|
||||
import ua.acclorite.book_story.core.log.logI
|
||||
import ua.acclorite.book_story.data.settings.SettingsManager
|
||||
import ua.acclorite.book_story.domain.model.file.File
|
||||
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
||||
import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetFilesUseCase @Inject constructor(
|
||||
private val fileSystemRepository: FileSystemRepository,
|
||||
private val settings: SettingsManager
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(query: String = ""): List<File> {
|
||||
logI("Searching for files with query: \"$query\".")
|
||||
|
||||
fun List<File>.filterFiles(): List<File> {
|
||||
return if (settings.browseIncludedFilterItems.lastValue.isEmpty()) this
|
||||
else filter { file ->
|
||||
settings.browseIncludedFilterItems.lastValue.any { extension ->
|
||||
file.path.endsWith(
|
||||
extension, ignoreCase = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<File>.sortFiles(): List<File> {
|
||||
return sortedWith(
|
||||
compareByWithOrder(settings.browseSortOrderDescending.lastValue) { file ->
|
||||
when (settings.browseSortOrder.lastValue) {
|
||||
BrowseSortOrder.NAME -> file.name.trim()
|
||||
|
||||
BrowseSortOrder.FILE_FORMAT -> file.path
|
||||
.substringAfterLast(".")
|
||||
.lowercase()
|
||||
.trimEnd()
|
||||
|
||||
BrowseSortOrder.FILE_SIZE -> file.size
|
||||
|
||||
else -> file.lastModified
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
fileSystemRepository.searchFiles(query)
|
||||
.getOrThrow()
|
||||
.filterFiles()
|
||||
.sortFiles()
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
logI("Successfully found [${it.size}] files.")
|
||||
it
|
||||
},
|
||||
onFailure = {
|
||||
logE("Could not find files with error: ${it.message}")
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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.domain.use_case.file_system
|
||||
|
||||
import ua.acclorite.book_story.core.log.logE
|
||||
import ua.acclorite.book_story.core.log.logI
|
||||
import ua.acclorite.book_story.domain.model.file.File
|
||||
import ua.acclorite.book_story.domain.repository.FileSystemRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchFilesUseCase @Inject constructor(
|
||||
private val fileSystemRepository: FileSystemRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(query: String = ""): List<File> {
|
||||
logI("Searching for files with query: \"$query\".")
|
||||
|
||||
return fileSystemRepository.searchFiles(query).fold(
|
||||
onSuccess = {
|
||||
logI("Successfully found [${it.size}] files.")
|
||||
it
|
||||
},
|
||||
onFailure = {
|
||||
logE("Could not find files with error: ${it.message}")
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.browse
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed class BrowseEffect {
|
||||
data object OnRequestFocus : BrowseEffect()
|
||||
|
||||
data object OnBooksAdded : BrowseEffect()
|
||||
|
||||
data object OnNavigateToLibrary : BrowseEffect()
|
||||
|
||||
data class OnUpdatePinnedPaths(
|
||||
val path: String
|
||||
) : BrowseEffect()
|
||||
|
||||
data object OnNavigateToBrowseSettings : BrowseEffect()
|
||||
}
|
||||
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
package ua.acclorite.book_story.presentation.browse
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import ua.acclorite.book_story.presentation.browse.model.SelectableFile
|
||||
import ua.acclorite.book_story.presentation.library.model.SelectableNullableBook
|
||||
|
||||
|
|
@ -23,9 +21,7 @@ sealed class BrowseEvent {
|
|||
val show: Boolean
|
||||
) : BrowseEvent()
|
||||
|
||||
data class OnRequestFocus(
|
||||
val focusRequester: FocusRequester
|
||||
) : BrowseEvent()
|
||||
data object OnRequestFocus : BrowseEvent()
|
||||
|
||||
data class OnSearchQueryChange(
|
||||
val query: String
|
||||
|
|
@ -36,15 +32,9 @@ sealed class BrowseEvent {
|
|||
data object OnClearSelectedFiles : BrowseEvent()
|
||||
|
||||
data class OnSelectFiles(
|
||||
val includedFileFormats: List<String>,
|
||||
val files: List<SelectableFile>
|
||||
) : BrowseEvent()
|
||||
|
||||
data class OnSelectFile(
|
||||
val includedFileFormats: List<String>,
|
||||
val file: SelectableFile
|
||||
) : BrowseEvent()
|
||||
|
||||
data object OnShowFilterBottomSheet : BrowseEvent()
|
||||
|
||||
data object OnDismissBottomSheet : BrowseEvent()
|
||||
|
|
@ -57,10 +47,15 @@ sealed class BrowseEvent {
|
|||
val book: SelectableNullableBook
|
||||
) : BrowseEvent()
|
||||
|
||||
data class OnActionAddDialog(
|
||||
val context: Context,
|
||||
val navigateToLibrary: () -> Unit
|
||||
) : BrowseEvent()
|
||||
data object OnActionAddDialog : BrowseEvent()
|
||||
|
||||
data object OnDismissDialog : BrowseEvent()
|
||||
|
||||
data class OnUpdatePinnedPaths(
|
||||
val path: String
|
||||
) : BrowseEvent()
|
||||
|
||||
data object OnNavigateToLibrary : BrowseEvent()
|
||||
|
||||
data object OnNavigateToBrowseSettings : BrowseEvent()
|
||||
}
|
||||
|
|
@ -13,7 +13,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
|
||||
|
|
@ -21,25 +24,21 @@ 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.core.helpers.compareByWithOrder
|
||||
import ua.acclorite.book_story.data.model.common.NullableBook
|
||||
import ua.acclorite.book_story.domain.model.file.File
|
||||
import ua.acclorite.book_story.domain.use_case.book.AddBookUseCase
|
||||
import ua.acclorite.book_story.domain.use_case.file_system.GetBookFromFileUseCase
|
||||
import ua.acclorite.book_story.domain.use_case.file_system.SearchFilesUseCase
|
||||
import ua.acclorite.book_story.presentation.browse.model.BrowseSortOrder
|
||||
import ua.acclorite.book_story.domain.use_case.file_system.GetFilesUseCase
|
||||
import ua.acclorite.book_story.presentation.browse.model.SelectableFile
|
||||
import ua.acclorite.book_story.presentation.library.LibraryScreen
|
||||
import ua.acclorite.book_story.presentation.library.model.SelectableNullableBook
|
||||
import ua.acclorite.book_story.ui.common.helpers.showToast
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@HiltViewModel
|
||||
class BrowseModel @Inject constructor(
|
||||
private val addBookUseCase: AddBookUseCase,
|
||||
private val searchFilesUseCase: SearchFilesUseCase,
|
||||
private val getFilesUseCase: GetFilesUseCase,
|
||||
private val getBookFromFileUseCase: GetBookFromFileUseCase
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -48,18 +47,19 @@ class BrowseModel @Inject constructor(
|
|||
private val _state = MutableStateFlow(BrowseState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<BrowseEffect>()
|
||||
val effects = _effects.asSharedFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
loading = true,
|
||||
hideSearch = true
|
||||
)
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
loading = true,
|
||||
hideSearch = true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/* Observe channel - - - - - - - - - - - */
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch {
|
||||
BrowseScreen.refreshListChannel.receiveAsFlow().collectLatest {
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
|
|
@ -73,37 +73,57 @@ class BrowseModel @Inject constructor(
|
|||
}
|
||||
|
||||
private var refreshJob: Job? = null
|
||||
private var changeSearchQueryJob: Job? = null
|
||||
private var searchQueryChangeJob: Job? = null
|
||||
private var getAddDialogBooksJob: Job? = null
|
||||
|
||||
fun onEvent(event: BrowseEvent) {
|
||||
when (event) {
|
||||
is BrowseEvent.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 BrowseEvent.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()
|
||||
getFilesFromDownloads()
|
||||
ensureActive()
|
||||
val selectableFiles = getFilesUseCase(
|
||||
if (_state.value.showSearch) _state.value.searchQuery
|
||||
else ""
|
||||
).map { file ->
|
||||
SelectableFile(
|
||||
name = file.name,
|
||||
uri = file.uri.toUri(),
|
||||
path = file.path,
|
||||
size = file.size,
|
||||
lastModified = file.lastModified,
|
||||
isDirectory = file.isDirectory,
|
||||
selected = false
|
||||
)
|
||||
}
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = selectableFiles,
|
||||
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 BrowseEvent.OnSearchVisibility -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
is BrowseEvent.OnSearchVisibility -> {
|
||||
if (!event.show) {
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
|
|
@ -111,27 +131,20 @@ class BrowseModel @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 BrowseEvent.OnRequestFocus -> {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
is BrowseEvent.OnRequestFocus -> {
|
||||
if (!_state.value.hasFocused) {
|
||||
event.focusRequester.requestFocus()
|
||||
_effects.emit(BrowseEffect.OnRequestFocus)
|
||||
_state.update {
|
||||
it.copy(
|
||||
hasFocused = true
|
||||
|
|
@ -139,26 +152,22 @@ class BrowseModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnSearchQueryChange -> {
|
||||
viewModelScope.launch {
|
||||
is BrowseEvent.OnSearchQueryChange -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
searchQuery = event.query
|
||||
)
|
||||
}
|
||||
changeSearchQueryJob?.cancel()
|
||||
changeSearchQueryJob = launch(Dispatchers.IO) {
|
||||
|
||||
searchQueryChangeJob?.cancel()
|
||||
searchQueryChangeJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
delay(500)
|
||||
yield()
|
||||
onEvent(BrowseEvent.OnSearch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnSearch -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
is BrowseEvent.OnSearch -> {
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
loading = false,
|
||||
|
|
@ -166,106 +175,62 @@ class BrowseModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnClearSelectedFiles -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = it.files.map { file -> file.copy(selected = false) },
|
||||
hasSelectedItems = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnSelectFiles -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val editedList = _state.value.files.map { file ->
|
||||
if (
|
||||
event.files.any {
|
||||
file.path.startsWith(it.path)
|
||||
} && event.includedFileFormats.run {
|
||||
if (isEmpty()) return@run true
|
||||
any {
|
||||
file.path.endsWith(it, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
) {
|
||||
file.copy(selected = true)
|
||||
} else {
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = editedList,
|
||||
selectedItemsCount = editedList.filter { file ->
|
||||
file.selected
|
||||
}.size.run {
|
||||
if (this == 0) return@run it.selectedItemsCount
|
||||
this
|
||||
},
|
||||
hasSelectedItems = editedList.any { file ->
|
||||
file.selected
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnSelectFile -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val editedList = _state.value.files.map { file ->
|
||||
if (event.file.path == file.path) {
|
||||
file.copy(
|
||||
selected = !file.selected
|
||||
is BrowseEvent.OnClearSelectedFiles -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = it.files.map { file -> file.copy(selected = false) },
|
||||
hasSelectedItems = false
|
||||
)
|
||||
} else {
|
||||
file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = editedList,
|
||||
selectedItemsCount = editedList.filter { file ->
|
||||
file.selected
|
||||
}.size.run {
|
||||
if (this == 0) return@run it.selectedItemsCount
|
||||
this
|
||||
},
|
||||
hasSelectedItems = editedList.any { file ->
|
||||
file.selected
|
||||
}
|
||||
)
|
||||
is BrowseEvent.OnSelectFiles -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
val editedList = _state.value.files.map { file ->
|
||||
if (event.files.any { file.path.startsWith(it.path, true) }) {
|
||||
file.copy(
|
||||
selected = if (event.files.size > 1) true else !file.selected
|
||||
)
|
||||
} else file
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = editedList,
|
||||
selectedItemsCount = editedList.filter { file ->
|
||||
file.selected
|
||||
}.size.let { selectedItems ->
|
||||
if (selectedItems == 0) return@let it.selectedItemsCount
|
||||
selectedItems
|
||||
},
|
||||
hasSelectedItems = editedList.any { file ->
|
||||
file.selected
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnShowFilterBottomSheet -> {
|
||||
viewModelScope.launch {
|
||||
is BrowseEvent.OnShowFilterBottomSheet -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
bottomSheet = BrowseScreen.FILTER_BOTTOM_SHEET
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnDismissBottomSheet -> {
|
||||
viewModelScope.launch {
|
||||
is BrowseEvent.OnDismissBottomSheet -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
bottomSheet = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnShowAddDialog -> {
|
||||
viewModelScope.launch {
|
||||
is BrowseEvent.OnShowAddDialog -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = BrowseScreen.ADD_DIALOG,
|
||||
|
|
@ -273,62 +238,51 @@ class BrowseModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
getAddDialogBooksJob = launch(Dispatchers.IO) {
|
||||
getAddDialogBooksJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
loadingAddDialog = true
|
||||
)
|
||||
}
|
||||
|
||||
yield()
|
||||
val books = _state.value.files.mapNotNull { file ->
|
||||
ensureActive()
|
||||
if (!file.selected) return@mapNotNull null
|
||||
SelectableNullableBook(
|
||||
data = getBookFromFileUseCase(
|
||||
File(
|
||||
name = file.name,
|
||||
uri = file.uri.toString(),
|
||||
path = file.path,
|
||||
size = file.size,
|
||||
lastModified = file.lastModified,
|
||||
isDirectory = file.isDirectory
|
||||
)
|
||||
),
|
||||
selected = true
|
||||
)
|
||||
}
|
||||
|
||||
val books = mutableListOf<NullableBook>()
|
||||
_state.value.files
|
||||
.filter { it.selected }
|
||||
.ifEmpty {
|
||||
_state.update {
|
||||
it.copy(
|
||||
loadingAddDialog = false,
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
.forEach {
|
||||
yield()
|
||||
books.add(
|
||||
getBookFromFileUseCase(
|
||||
File(
|
||||
name = it.name,
|
||||
uri = it.uri.toString(),
|
||||
path = it.path,
|
||||
size = it.size,
|
||||
lastModified = it.lastModified,
|
||||
isDirectory = it.isDirectory
|
||||
)
|
||||
)
|
||||
if (books.isEmpty()) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
loadingAddDialog = false,
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
|
||||
yield()
|
||||
return@launch
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedBooksAddDialog = books.map { book ->
|
||||
SelectableNullableBook(
|
||||
data = book,
|
||||
selected = true
|
||||
)
|
||||
},
|
||||
selectedBooksAddDialog = books,
|
||||
loadingAddDialog = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnDismissAddDialog -> {
|
||||
viewModelScope.launch {
|
||||
is BrowseEvent.OnDismissAddDialog -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = null
|
||||
|
|
@ -336,160 +290,94 @@ class BrowseModel @Inject constructor(
|
|||
}
|
||||
getAddDialogBooksJob?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnActionAddDialog -> {
|
||||
viewModelScope.launch {
|
||||
val booksToInsert = _state.value.selectedBooksAddDialog.mapNotNull {
|
||||
if (it.data is NullableBook.NotNull && it.selected) {
|
||||
return@mapNotNull it.data
|
||||
}
|
||||
return@mapNotNull null
|
||||
}.ifEmpty { return@launch }
|
||||
|
||||
for (book in booksToInsert) {
|
||||
book.bookWithCover?.let {
|
||||
is BrowseEvent.OnActionAddDialog -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
_state.value.selectedBooksAddDialog.mapNotNull {
|
||||
if (it.data is NullableBook.NotNull && it.selected) return@mapNotNull it.data.bookWithCover
|
||||
return@mapNotNull null
|
||||
}.ifEmpty { return@withContext }.forEach { bookWithCover ->
|
||||
addBookUseCase(
|
||||
it.book,
|
||||
it.coverImage
|
||||
bookWithCover.book,
|
||||
bookWithCover.coverImage
|
||||
)
|
||||
}
|
||||
|
||||
LibraryScreen.refreshListChannel.trySend(0)
|
||||
LibraryScreen.scrollToPageCompositionChannel.trySend(0)
|
||||
|
||||
_effects.emit(BrowseEffect.OnNavigateToLibrary)
|
||||
_effects.emit(BrowseEffect.OnBooksAdded)
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
loading = false,
|
||||
hideSearch = false
|
||||
)
|
||||
)
|
||||
onEvent(BrowseEvent.OnClearSelectedFiles)
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnSelectAddDialog -> {
|
||||
withContext(Dispatchers.Default) {
|
||||
val index = _state.value.selectedBooksAddDialog.indexOf(event.book)
|
||||
if (index == -1) return@withContext
|
||||
|
||||
val editedList = _state.value.selectedBooksAddDialog.toMutableList()
|
||||
editedList[index] = editedList[index].let {
|
||||
it.copy(
|
||||
selected = !it.selected
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
!editedList
|
||||
.filter { it.data is NullableBook.NotNull }
|
||||
.any { it.selected }
|
||||
) {
|
||||
return@withContext
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedBooksAddDialog = editedList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LibraryScreen.refreshListChannel.trySend(0)
|
||||
LibraryScreen.scrollToPageCompositionChannel.trySend(0)
|
||||
|
||||
event.navigateToLibrary()
|
||||
withContext(Dispatchers.Main) {
|
||||
event.context
|
||||
.getString(R.string.books_added)
|
||||
.showToast(context = event.context)
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
loading = false,
|
||||
hideSearch = false
|
||||
)
|
||||
)
|
||||
onEvent(BrowseEvent.OnClearSelectedFiles)
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnSelectAddDialog -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val index = _state.value.selectedBooksAddDialog.indexOf(event.book).apply {
|
||||
if (this == -1) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
val editedList = _state.value.selectedBooksAddDialog.toMutableList()
|
||||
editedList[index] = editedList[index].run { copy(selected = !selected) }
|
||||
|
||||
if (!editedList.filter { it.data is NullableBook.NotNull }
|
||||
.any { it.selected }) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
selectedBooksAddDialog = editedList
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BrowseEvent.OnDismissDialog -> {
|
||||
viewModelScope.launch {
|
||||
is BrowseEvent.OnDismissDialog -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialog = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFilesFromDownloads(
|
||||
query: String = if (_state.value.showSearch) _state.value.searchQuery else ""
|
||||
) {
|
||||
searchFilesUseCase(query).apply {
|
||||
yield()
|
||||
_state.update {
|
||||
it.copy(
|
||||
files = map {
|
||||
SelectableFile(
|
||||
name = it.name,
|
||||
uri = it.uri.toUri(),
|
||||
path = it.path,
|
||||
size = it.size,
|
||||
lastModified = it.lastModified,
|
||||
isDirectory = it.isDirectory,
|
||||
selected = false
|
||||
)
|
||||
},
|
||||
hasSelectedItems = false,
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is BrowseEvent.OnUpdatePinnedPaths -> {
|
||||
_effects.emit(BrowseEffect.OnUpdatePinnedPaths(event.path))
|
||||
}
|
||||
|
||||
fun filterList(
|
||||
files: List<SelectableFile>,
|
||||
sortOrderDescending: Boolean,
|
||||
includedFilterItems: List<String>,
|
||||
sortOrder: BrowseSortOrder
|
||||
): List<SelectableFile> {
|
||||
fun List<SelectableFile>.filterFiles(): List<SelectableFile> {
|
||||
if (includedFilterItems.isEmpty()) {
|
||||
return this
|
||||
}
|
||||
is BrowseEvent.OnNavigateToLibrary -> {
|
||||
_effects.emit(BrowseEffect.OnNavigateToLibrary)
|
||||
}
|
||||
|
||||
return filter { file ->
|
||||
includedFilterItems.any {
|
||||
file.path.endsWith(
|
||||
it, ignoreCase = true
|
||||
)
|
||||
is BrowseEvent.OnNavigateToBrowseSettings -> {
|
||||
_effects.emit(BrowseEffect.OnNavigateToBrowseSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
.filterFiles()
|
||||
.sortedWith(
|
||||
compareByWithOrder(sortOrderDescending) {
|
||||
when (sortOrder) {
|
||||
BrowseSortOrder.NAME -> {
|
||||
it.name.trim()
|
||||
}
|
||||
|
||||
BrowseSortOrder.FILE_FORMAT -> {
|
||||
it.path.substringAfterLast(".").lowercase().trimEnd()
|
||||
}
|
||||
|
||||
BrowseSortOrder.FILE_SIZE -> {
|
||||
it.size
|
||||
}
|
||||
|
||||
else -> {
|
||||
it.lastModified
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
|
||||
mutex.withLock {
|
||||
yield()
|
||||
coroutineContext.ensureActive()
|
||||
this.value = function(this.value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +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.core.helpers.toggle
|
||||
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
|
||||
import ua.acclorite.book_story.presentation.library.LibraryScreen
|
||||
import ua.acclorite.book_story.presentation.navigator.Screen
|
||||
import ua.acclorite.book_story.presentation.settings.BrowseSettingsScreen
|
||||
import ua.acclorite.book_story.ui.browse.BrowseContent
|
||||
import ua.acclorite.book_story.ui.browse.BrowseEffects
|
||||
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
|
||||
import ua.acclorite.book_story.ui.navigator.LocalNavigator
|
||||
|
||||
@Parcelize
|
||||
object BrowseScreen : Screen, Parcelable {
|
||||
|
|
@ -62,7 +59,6 @@ object BrowseScreen : Screen, Parcelable {
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.current
|
||||
val screenModel = hiltViewModel<BrowseModel>()
|
||||
val settings = LocalSettings.current
|
||||
|
||||
|
|
@ -84,17 +80,16 @@ object BrowseScreen : Screen, Parcelable {
|
|||
}
|
||||
)
|
||||
|
||||
val files = remember(
|
||||
state.value.files,
|
||||
LaunchedEffect(
|
||||
settings.browseIncludedFilterItems.value,
|
||||
settings.browseSortOrderDescending.value,
|
||||
settings.browseSortOrder.value
|
||||
) {
|
||||
screenModel.filterList(
|
||||
files = state.value.files,
|
||||
sortOrderDescending = settings.browseSortOrderDescending.lastValue,
|
||||
includedFilterItems = settings.browseIncludedFilterItems.lastValue,
|
||||
sortOrder = settings.browseSortOrder.lastValue
|
||||
screenModel.onEvent(
|
||||
BrowseEvent.OnRefreshList(
|
||||
loading = false,
|
||||
hideSearch = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -128,8 +123,13 @@ object BrowseScreen : Screen, Parcelable {
|
|||
}
|
||||
}
|
||||
|
||||
BrowseEffects(
|
||||
effects = screenModel.effects,
|
||||
focusRequester = focusRequester
|
||||
)
|
||||
|
||||
BrowseContent(
|
||||
files = files,
|
||||
files = state.value.files,
|
||||
selectedBooksAddDialog = state.value.selectedBooksAddDialog,
|
||||
refreshState = refreshState,
|
||||
loadingAddDialog = state.value.loadingAddDialog,
|
||||
|
|
@ -149,7 +149,7 @@ object BrowseScreen : Screen, Parcelable {
|
|||
isRefreshing = state.value.isRefreshing,
|
||||
isLoading = state.value.isLoading,
|
||||
dialogHidden = state.value.dialog == null,
|
||||
filesEmpty = files.isEmpty(),
|
||||
filesEmpty = state.value.files.isEmpty(),
|
||||
showSearch = state.value.showSearch,
|
||||
searchQuery = state.value.searchQuery,
|
||||
focusRequester = focusRequester,
|
||||
|
|
@ -159,24 +159,15 @@ object BrowseScreen : Screen, Parcelable {
|
|||
requestFocus = screenModel::onEvent,
|
||||
clearSelectedFiles = screenModel::onEvent,
|
||||
selectFiles = screenModel::onEvent,
|
||||
selectFile = screenModel::onEvent,
|
||||
showFilterBottomSheet = screenModel::onEvent,
|
||||
dismissBottomSheet = screenModel::onEvent,
|
||||
showAddDialog = screenModel::onEvent,
|
||||
dismissAddDialog = screenModel::onEvent,
|
||||
selectAddDialog = screenModel::onEvent,
|
||||
actionAddDialog = screenModel::onEvent,
|
||||
updatePinnedPaths = {
|
||||
settings.browsePinnedPaths.update(
|
||||
settings.browsePinnedPaths.lastValue.toggle(it)
|
||||
)
|
||||
},
|
||||
navigateToLibrary = {
|
||||
navigator.push(LibraryScreen, saveInBackStack = false)
|
||||
},
|
||||
navigateToBrowseSettings = {
|
||||
navigator.push(BrowseSettingsScreen)
|
||||
},
|
||||
updatePinnedPaths = screenModel::onEvent,
|
||||
navigateToLibrary = screenModel::onEvent,
|
||||
navigateToBrowseSettings = screenModel::onEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +34,7 @@ fun BrowseAddDialog(
|
|||
selectedBooksAddDialog: List<SelectableNullableBook>,
|
||||
dismissAddDialog: (BrowseEvent.OnDismissAddDialog) -> Unit,
|
||||
actionAddDialog: (BrowseEvent.OnActionAddDialog) -> Unit,
|
||||
selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit,
|
||||
navigateToLibrary: () -> Unit
|
||||
selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Dialog(
|
||||
|
|
@ -45,12 +44,7 @@ fun BrowseAddDialog(
|
|||
actionEnabled = !loadingAddDialog && selectedBooksAddDialog.any { it.data is NullableBook.NotNull },
|
||||
onDismiss = { dismissAddDialog(BrowseEvent.OnDismissAddDialog) },
|
||||
onAction = {
|
||||
actionAddDialog(
|
||||
BrowseEvent.OnActionAddDialog(
|
||||
context = context,
|
||||
navigateToLibrary = navigateToLibrary
|
||||
)
|
||||
)
|
||||
actionAddDialog(BrowseEvent.OnActionAddDialog)
|
||||
},
|
||||
withContent = true,
|
||||
items = {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ fun BrowseBackHandler(
|
|||
showSearch: Boolean,
|
||||
searchVisibility: (BrowseEvent.OnSearchVisibility) -> Unit,
|
||||
clearSelectedFiles: (BrowseEvent.OnClearSelectedFiles) -> Unit,
|
||||
navigateToLibrary: () -> Unit
|
||||
navigateToLibrary: (BrowseEvent.OnNavigateToLibrary) -> Unit
|
||||
) {
|
||||
BackHandler {
|
||||
if (hasSelectedItems) {
|
||||
|
|
@ -29,6 +29,6 @@ fun BrowseBackHandler(
|
|||
return@BackHandler
|
||||
}
|
||||
|
||||
navigateToLibrary()
|
||||
navigateToLibrary(BrowseEvent.OnNavigateToLibrary)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,16 +52,15 @@ fun BrowseContent(
|
|||
requestFocus: (BrowseEvent.OnRequestFocus) -> Unit,
|
||||
clearSelectedFiles: (BrowseEvent.OnClearSelectedFiles) -> Unit,
|
||||
selectFiles: (BrowseEvent.OnSelectFiles) -> Unit,
|
||||
selectFile: (BrowseEvent.OnSelectFile) -> Unit,
|
||||
dismissBottomSheet: (BrowseEvent.OnDismissBottomSheet) -> Unit,
|
||||
showFilterBottomSheet: (BrowseEvent.OnShowFilterBottomSheet) -> Unit,
|
||||
showAddDialog: (BrowseEvent.OnShowAddDialog) -> Unit,
|
||||
dismissAddDialog: (BrowseEvent.OnDismissAddDialog) -> Unit,
|
||||
actionAddDialog: (BrowseEvent.OnActionAddDialog) -> Unit,
|
||||
selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit,
|
||||
updatePinnedPaths: (String) -> Unit,
|
||||
navigateToLibrary: () -> Unit,
|
||||
navigateToBrowseSettings: () -> Unit,
|
||||
updatePinnedPaths: (BrowseEvent.OnUpdatePinnedPaths) -> Unit,
|
||||
navigateToLibrary: (BrowseEvent.OnNavigateToLibrary) -> Unit,
|
||||
navigateToBrowseSettings: (BrowseEvent.OnNavigateToBrowseSettings) -> Unit
|
||||
) {
|
||||
BrowseDialog(
|
||||
dialog = dialog,
|
||||
|
|
@ -69,8 +68,7 @@ fun BrowseContent(
|
|||
actionAddDialog = actionAddDialog,
|
||||
dismissAddDialog = dismissAddDialog,
|
||||
selectedBooksAddDialog = selectedBooksAddDialog,
|
||||
selectAddDialog = selectAddDialog,
|
||||
navigateToLibrary = navigateToLibrary
|
||||
selectAddDialog = selectAddDialog
|
||||
)
|
||||
|
||||
BrowseBottomSheet(
|
||||
|
|
@ -105,7 +103,6 @@ fun BrowseContent(
|
|||
selectFiles = selectFiles,
|
||||
isLoading = isLoading,
|
||||
filesEmpty = filesEmpty,
|
||||
selectFile = selectFile,
|
||||
showFilterBottomSheet = showFilterBottomSheet,
|
||||
showAddDialog = showAddDialog,
|
||||
updatePinnedPaths = updatePinnedPaths,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ fun BrowseDialog(
|
|||
selectedBooksAddDialog: List<SelectableNullableBook>,
|
||||
dismissAddDialog: (BrowseEvent.OnDismissAddDialog) -> Unit,
|
||||
actionAddDialog: (BrowseEvent.OnActionAddDialog) -> Unit,
|
||||
selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit,
|
||||
navigateToLibrary: () -> Unit
|
||||
selectAddDialog: (BrowseEvent.OnSelectAddDialog) -> Unit
|
||||
) {
|
||||
when (dialog) {
|
||||
BrowseScreen.ADD_DIALOG -> {
|
||||
|
|
@ -29,8 +28,7 @@ fun BrowseDialog(
|
|||
selectedBooksAddDialog = selectedBooksAddDialog,
|
||||
dismissAddDialog = dismissAddDialog,
|
||||
actionAddDialog = actionAddDialog,
|
||||
selectAddDialog = selectAddDialog,
|
||||
navigateToLibrary = navigateToLibrary
|
||||
selectAddDialog = selectAddDialog
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.browse
|
||||
|
||||
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.core.helpers.toggle
|
||||
import ua.acclorite.book_story.presentation.browse.BrowseEffect
|
||||
import ua.acclorite.book_story.presentation.library.LibraryScreen
|
||||
import ua.acclorite.book_story.presentation.settings.BrowseSettingsScreen
|
||||
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
|
||||
import ua.acclorite.book_story.ui.common.helpers.showToast
|
||||
import ua.acclorite.book_story.ui.navigator.LocalNavigator
|
||||
|
||||
@Composable
|
||||
fun BrowseEffects(effects: SharedFlow<BrowseEffect>, focusRequester: FocusRequester) {
|
||||
val navigator = LocalNavigator.current
|
||||
val settings = LocalSettings.current
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(effects, focusRequester, context, navigator, settings.browsePinnedPaths.value) {
|
||||
effects.collect { effect ->
|
||||
when (effect) {
|
||||
is BrowseEffect.OnRequestFocus -> {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
|
||||
is BrowseEffect.OnBooksAdded -> {
|
||||
context.getString(R.string.books_added)
|
||||
.showToast(context = context)
|
||||
}
|
||||
|
||||
is BrowseEffect.OnNavigateToLibrary -> {
|
||||
navigator.push(LibraryScreen, saveInBackStack = false)
|
||||
}
|
||||
|
||||
is BrowseEffect.OnUpdatePinnedPaths -> {
|
||||
settings.browsePinnedPaths.update(
|
||||
settings.browsePinnedPaths.lastValue.toggle(effect.path)
|
||||
)
|
||||
}
|
||||
|
||||
is BrowseEffect.OnNavigateToBrowseSettings -> {
|
||||
navigator.push(BrowseSettingsScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,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.browse.BrowseEvent
|
||||
import ua.acclorite.book_story.ui.common.components.common.AnimatedVisibility
|
||||
import ua.acclorite.book_story.ui.common.components.placeholder.EmptyPlaceholder
|
||||
import ua.acclorite.book_story.ui.theme.Transitions
|
||||
|
|
@ -23,7 +24,7 @@ fun BoxScope.BrowseEmptyPlaceholder(
|
|||
dialogHidden: Boolean,
|
||||
isLoading: Boolean,
|
||||
isRefreshing: Boolean,
|
||||
navigateToBrowseSettings: () -> Unit
|
||||
navigateToBrowseSettings: (BrowseEvent.OnNavigateToBrowseSettings) -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !isLoading
|
||||
|
|
@ -39,7 +40,7 @@ fun BoxScope.BrowseEmptyPlaceholder(
|
|||
icon = painterResource(id = R.drawable.empty_browse),
|
||||
actionTitle = stringResource(id = R.string.set_up_scanning),
|
||||
action = {
|
||||
navigateToBrowseSettings()
|
||||
navigateToBrowseSettings(BrowseEvent.OnNavigateToBrowseSettings)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,11 +53,10 @@ fun BrowseScaffold(
|
|||
requestFocus: (BrowseEvent.OnRequestFocus) -> Unit,
|
||||
clearSelectedFiles: (BrowseEvent.OnClearSelectedFiles) -> Unit,
|
||||
selectFiles: (BrowseEvent.OnSelectFiles) -> Unit,
|
||||
selectFile: (BrowseEvent.OnSelectFile) -> Unit,
|
||||
showFilterBottomSheet: (BrowseEvent.OnShowFilterBottomSheet) -> Unit,
|
||||
showAddDialog: (BrowseEvent.OnShowAddDialog) -> Unit,
|
||||
updatePinnedPaths: (String) -> Unit,
|
||||
navigateToBrowseSettings: () -> Unit,
|
||||
updatePinnedPaths: (BrowseEvent.OnUpdatePinnedPaths) -> Unit,
|
||||
navigateToBrowseSettings: (BrowseEvent.OnNavigateToBrowseSettings) -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
|
|
@ -106,7 +105,11 @@ fun BrowseScaffold(
|
|||
header = header,
|
||||
pinned = pinned,
|
||||
pin = {
|
||||
updatePinnedPaths(header)
|
||||
updatePinnedPaths(
|
||||
BrowseEvent.OnUpdatePinnedPaths(
|
||||
path = header
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
|
@ -118,16 +121,14 @@ fun BrowseScaffold(
|
|||
onLongClick = {
|
||||
selectFiles(
|
||||
BrowseEvent.OnSelectFiles(
|
||||
includedFileFormats = includedFilterItems,
|
||||
files = groupFiles
|
||||
)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
selectFile(
|
||||
BrowseEvent.OnSelectFile(
|
||||
includedFileFormats = includedFilterItems,
|
||||
file = file
|
||||
selectFiles(
|
||||
BrowseEvent.OnSelectFiles(
|
||||
files = listOf(file)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ fun BrowseTopBar(
|
|||
modifier = Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.onGloballyPositioned {
|
||||
requestFocus(BrowseEvent.OnRequestFocus(focusRequester))
|
||||
requestFocus(BrowseEvent.OnRequestFocus)
|
||||
},
|
||||
initialQuery = searchQuery,
|
||||
onQueryChange = {
|
||||
|
|
@ -172,7 +172,6 @@ fun BrowseTopBar(
|
|||
) {
|
||||
selectFiles(
|
||||
BrowseEvent.OnSelectFiles(
|
||||
includedFileFormats = includedFilterItems,
|
||||
files = files
|
||||
)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue