From 80fbf74d9a8c6f44fa4cf3530ab49503bbd44816 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Wed, 19 Mar 2025 17:33:35 +0530 Subject: [PATCH] Created `LocalLibraryScreenState` to encapsulate all UI-related states, reducing state management's complexity. --- .../library/local/LocalLibraryFragment.kt | 174 ++++++++---------- .../library/local/LocalLibraryScreen.kt | 90 ++++----- .../library/local/LocalLibraryScreenState.kt | 73 ++++++++ 3 files changed, 193 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreenState.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt index 300477bc6..0b4192cab 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt @@ -40,9 +40,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.platform.ComposeView -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.net.toUri @@ -87,9 +85,6 @@ import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION import org.kiwix.kiwixmobile.core.utils.INTERNAL_SELECT_POSITION import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil -import org.kiwix.kiwixmobile.core.utils.SimpleRecyclerViewScrollListener -import org.kiwix.kiwixmobile.core.utils.SimpleRecyclerViewScrollListener.Companion.SCROLL_DOWN -import org.kiwix.kiwixmobile.core.utils.SimpleRecyclerViewScrollListener.Companion.SCROLL_UP import org.kiwix.kiwixmobile.core.utils.TAG_KIWIX import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog @@ -97,7 +92,6 @@ import org.kiwix.kiwixmobile.core.utils.files.FileUtils import org.kiwix.kiwixmobile.core.utils.files.FileUtils.isSplittedZimFile import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk -import org.kiwix.kiwixmobile.databinding.FragmentDestinationLibraryBinding import org.kiwix.kiwixmobile.main.KiwixMainActivity import org.kiwix.kiwixmobile.nav.destination.library.CopyMoveFileHandler import org.kiwix.kiwixmobile.zimManager.MAX_PROGRESS @@ -134,34 +128,19 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal private var actionMode: ActionMode? = null private val disposable = CompositeDisposable() - private var fragmentDestinationLibraryBinding: FragmentDestinationLibraryBinding? = null private var permissionDeniedLayoutShowing = false private var zimFileUri: Uri? = null - private lateinit var snackBarHostState: SnackbarHostState - private var fileSelectListState = mutableStateOf(FileSelectListState(emptyList())) - - /** - * This is a Triple which is responsible for showing and hiding the "No file here", - * and "Download books" button. - * - * A [Triple] containing: - * - [String]: The title text displayed when no files are available. - * - [String]: The label for the download button. - * - [Boolean]: The boolean value for showing or hiding this view. - */ - private var noFilesViewItem = mutableStateOf(Triple("", "", false)) - - /** - * This is a Pair which is responsible for showing and hiding the "Pull to refresh" - * animation. - * - * A [Pair] containing: - * - [Boolean]: The first boolean triggers/hide the "pull to refresh" animation. - * - [Boolean]: The second boolean enable/disable the "pull to refresh". - */ - private var swipeRefreshItem = mutableStateOf(Pair(false, true)) - - private var scanningProgressItem = mutableStateOf(Pair(false, ZERO)) + val libraryScreenState = mutableStateOf( + LocalLibraryScreenState( + fileSelectListState = FileSelectListState(emptyList()), + snackBarHostState = SnackbarHostState(), + swipeRefreshItem = Pair(false, true), + scanningProgressItem = Pair(false, ZERO), + noFilesViewItem = Triple("", "", false), + actionMenuItems = listOf(), + bottomNavigationHeight = ZERO + ) + ) private val zimManageViewModel by lazy { requireActivity().viewModel(viewModelFactory) @@ -174,10 +153,12 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal val isGranted = permissionResult.values.all { it } val isPermanentlyDenied = readStorageHasBeenPermanentlyDenied(isGranted) permissionDeniedLayoutShowing = isPermanentlyDenied - noFilesViewItem.value = Triple( - requireActivity().resources.getString(string.grant_read_storage_permission), - requireActivity().resources.getString(string.go_to_settings_label), - isPermanentlyDenied + updateLibraryScreenState( + noFilesViewItem = Triple( + requireActivity().resources.getString(string.grant_read_storage_permission), + requireActivity().resources.getString(string.go_to_settings_label), + isPermanentlyDenied + ) ) } @@ -195,20 +176,18 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal val composeView = ComposeView(requireContext()).apply { setContent { - snackBarHostState = remember { SnackbarHostState() } + updateLibraryScreenState( + bottomNavigationHeight = getBottomNavigationHeight(), + actionMenuItems = actionMenuItems() + ) LocalLibraryScreen( - state = fileSelectListState.value, - snackBarHostState = snackBarHostState, + state = libraryScreenState.value, fabButtonClick = { filePickerButtonClick() }, - actionMenuItems = actionMenuItems(), onClick = { onBookItemClick(it) }, onLongClick = { onBookItemLongClick(it) }, onMultiSelect = { offerAction(RequestSelect(it)) }, onRefresh = { onSwipeRefresh() }, - swipeRefreshItem = swipeRefreshItem.value, - noFilesViewItem = noFilesViewItem.value, onDownloadButtonClick = { downloadBookButtonClick() }, - scanningProgressItem = scanningProgressItem.value ) { NavigationIcon( iconItem = IconItem.Vector(Icons.Filled.Menu), @@ -260,6 +239,28 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal } } + private fun updateLibraryScreenState( + fileSelectListState: FileSelectListState? = null, + snackBarHostState: SnackbarHostState? = null, + swipeRefreshItem: Pair? = null, + scanningProgressItem: Pair? = null, + noFilesViewItem: Triple? = null, + actionMenuItems: List? = null, + bottomNavigationHeight: Int? = null + ) { + libraryScreenState.value = libraryScreenState.value.copy( + fileSelectListState = fileSelectListState ?: libraryScreenState.value.fileSelectListState, + snackBarHostState = snackBarHostState ?: libraryScreenState.value.snackBarHostState, + swipeRefreshItem = swipeRefreshItem ?: libraryScreenState.value.swipeRefreshItem, + scanningProgressItem = scanningProgressItem + ?: libraryScreenState.value.scanningProgressItem, + noFilesViewItem = noFilesViewItem ?: libraryScreenState.value.noFilesViewItem, + actionMenuItems = actionMenuItems ?: libraryScreenState.value.actionMenuItems, + bottomNavigationHeight = bottomNavigationHeight + ?: libraryScreenState.value.bottomNavigationHeight + ) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) copyMoveFileHandler?.apply { @@ -270,38 +271,20 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal .also { coreMainActivity.navHostContainer .setBottomMarginToFragmentContainerView(0) - - getBottomNavigationView()?.let { bottomNavigationView -> - setBottomMarginToSwipeRefreshLayout(bottomNavigationView.measuredHeight) - } } disposable.add(sideEffects()) disposable.add(fileSelectActions()) zimManageViewModel.deviceListScanningProgress.observe(viewLifecycleOwner) { - // hide this progress bar when scanning is complete. - scanningProgressItem.value = Pair(it != MAX_PROGRESS, it) - // enable if the previous scanning is completes. - swipeRefreshItem.value = Pair(false, it == MAX_PROGRESS) + updateLibraryScreenState( + // hide this progress bar when scanning is complete. + scanningProgressItem = Pair(it != MAX_PROGRESS, it), + // enable if the previous scanning is completes. + swipeRefreshItem = Pair(false, it == MAX_PROGRESS) + ) } if (savedInstanceState != null && savedInstanceState.getBoolean(WAS_IN_ACTION_MODE)) { zimManageViewModel.fileSelectActions.offer(FileSelectActions.RestartActionMode) } - - fragmentDestinationLibraryBinding?.zimfilelist?.addOnScrollListener( - SimpleRecyclerViewScrollListener { _, newState -> - when (newState) { - SCROLL_DOWN -> { - setBottomMarginToSwipeRefreshLayout(0) - } - - SCROLL_UP -> { - getBottomNavigationView()?.let { - setBottomMarginToSwipeRefreshLayout(it.measuredHeight) - } - } - } - } - ) showCopyMoveDialogForOpenedZimFileFromStorage() } @@ -325,22 +308,24 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal private fun onSwipeRefresh() { if (permissionDeniedLayoutShowing) { // When permission denied layout is showing hide the "Swipe refresh". - swipeRefreshItem.value = false to true + updateLibraryScreenState(swipeRefreshItem = false to true) } else { if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) { showManageExternalStoragePermissionDialog() // Set loading to false since the dialog is currently being displayed. // If the user clicks on "No" in the permission dialog, // the loading icon remains visible infinitely. - swipeRefreshItem.value = false to true + updateLibraryScreenState(swipeRefreshItem = false to true) } else { // hide the swipe refreshing because now we are showing the ContentLoadingProgressBar // to show the progress of how many files are scanned. // disable the swipe refresh layout until the ongoing scanning will not complete // to avoid multiple scanning. - swipeRefreshItem.value = false to false - // Show the progress Bar. - scanningProgressItem.value = true to ZERO + updateLibraryScreenState( + swipeRefreshItem = false to false, + // Show the progress Bar. + scanningProgressItem = true to ZERO + ) requestFileSystemCheck() } } @@ -360,13 +345,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal private fun getBottomNavigationView() = requireActivity().findViewById(R.id.bottom_nav_view) - private fun setBottomMarginToSwipeRefreshLayout(marginBottom: Int) { - fragmentDestinationLibraryBinding?.zimSwiperefresh?.apply { - val params = layoutParams as CoordinatorLayout.LayoutParams? - params?.bottomMargin = marginBottom - requestLayout() - } - } + private fun getBottomNavigationHeight() = getBottomNavigationView().measuredHeight private fun filePickerButtonClick() { if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) { @@ -510,10 +489,12 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal ) { checkPermissions() } else if (!permissionDeniedLayoutShowing) { - noFilesViewItem.value = Triple( - requireActivity().resources.getString(string.no_files_here), - requireActivity().resources.getString(string.download_books), - false + updateLibraryScreenState( + noFilesViewItem = Triple( + requireActivity().resources.getString(string.no_files_here), + requireActivity().resources.getString(string.download_books), + false + ) ) } } @@ -540,7 +521,11 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal val effectResult = it.invokeWith(requireActivity() as AppCompatActivity) if (effectResult is ActionMode) { actionMode = effectResult - fileSelectListState.value.selectedBooks.size.let(::setActionModeTitle) + libraryScreenState + .value + .fileSelectListState + .selectedBooks + .size.let(::setActionModeTitle) } }, Throwable::printStackTrace @@ -568,20 +553,21 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal // Force recomposition by first setting an empty list before assigning the updated list. // This is necessary because modifying an object's property doesn't trigger recomposition, // as Compose still considers the list unchanged. - fileSelectListState.value = FileSelectListState(emptyList()) - fileSelectListState.value = state + updateLibraryScreenState( + fileSelectListState = state, + noFilesViewItem = Triple( + requireActivity().resources.getString(string.no_files_here), + requireActivity().resources.getString(string.download_books), + // If here are no items available then show the "No files here" text, and "Download books" + // button so that user can go to "Online library" screen by clicking this button. + state.bookOnDiskListItems.isEmpty() + ) + ) if (state.bookOnDiskListItems.none(BooksOnDiskListItem::isSelected)) { actionMode?.finish() actionMode = null } setActionModeTitle(state.selectedBooks.size) - noFilesViewItem.value = Triple( - requireActivity().resources.getString(string.no_files_here), - requireActivity().resources.getString(string.download_books), - // If here are no items available then show the "No files here" text, and "Download books" - // button so that user can go to "Online library" screen by clicking this button. - state.bookOnDiskListItems.isEmpty() - ) } private fun setActionModeTitle(selectedBookCount: Int) { @@ -679,7 +665,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal } private fun showStorageSelectionSnackBar(message: String) { - snackBarHostState.snack( + libraryScreenState.value.snackBarHostState.snack( message = message, actionLabel = getString(string.download_change_storage), lifecycleScope = lifecycleScope, diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt index 31db0b7d3..d48e88867 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt @@ -26,35 +26,42 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp import org.kiwix.kiwixmobile.R.string import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar import org.kiwix.kiwixmobile.core.ui.components.KiwixButton import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle import org.kiwix.kiwixmobile.core.ui.components.SwipeRefreshLayout -import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem import org.kiwix.kiwixmobile.core.ui.theme.Black import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme import org.kiwix.kiwixmobile.core.ui.theme.White import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP -import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FAB_ICON_BOTTOM_MARGIN import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP +import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TEN_DP import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.ui.BookItem @@ -62,54 +69,65 @@ import org.kiwix.kiwixmobile.ui.ZimFilesLanguageHeader import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState @OptIn(ExperimentalMaterial3Api::class) -@Suppress("ComposableLambdaParameterNaming", "LongParameterList") +@Suppress("ComposableLambdaParameterNaming") @Composable fun LocalLibraryScreen( - state: FileSelectListState, - snackBarHostState: SnackbarHostState, - swipeRefreshItem: Pair, + state: LocalLibraryScreenState, onRefresh: () -> Unit, - scanningProgressItem: Pair, - noFilesViewItem: Triple, onDownloadButtonClick: () -> Unit, fabButtonClick: () -> Unit, - actionMenuItems: List, onClick: ((BookOnDisk) -> Unit)? = null, onLongClick: ((BookOnDisk) -> Unit)? = null, onMultiSelect: ((BookOnDisk) -> Unit)? = null, navigationIcon: @Composable () -> Unit ) { + val lazyListState = rememberLazyListState() + val bottomNavHeightInDp = with(LocalDensity.current) { state.bottomNavigationHeight.toDp() } + val bottomNavHeight = remember { mutableStateOf(bottomNavHeightInDp) } + LaunchedEffect(lazyListState) { + snapshotFlow { lazyListState.firstVisibleItemScrollOffset } + .collect { scrollOffset -> + bottomNavHeight.value = if (scrollOffset > 0) ZERO.dp else bottomNavHeightInDp + } + } KiwixTheme { Scaffold( - snackbarHost = { KiwixSnackbarHost(snackbarHostState = snackBarHostState) }, - topBar = { KiwixAppBar(R.string.library, navigationIcon, actionMenuItems) }, + snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) }, + topBar = { KiwixAppBar(R.string.library, navigationIcon, state.actionMenuItems) }, modifier = Modifier.systemBarsPadding() ) { contentPadding -> SwipeRefreshLayout( - isRefreshing = swipeRefreshItem.first, - isEnabled = swipeRefreshItem.second, + isRefreshing = state.swipeRefreshItem.first, + isEnabled = state.swipeRefreshItem.second, onRefresh = onRefresh, modifier = Modifier .fillMaxSize() .padding(contentPadding) + .padding(bottom = bottomNavHeight.value) ) { - if (scanningProgressItem.first) { + if (state.scanningProgressItem.first) { ContentLoadingProgressBar( progressBarStyle = ProgressBarStyle.HORIZONTAL, - progress = scanningProgressItem.second + progress = state.scanningProgressItem.second ) } - if (noFilesViewItem.third) { - NoFilesView(noFilesViewItem, onDownloadButtonClick) + if (state.noFilesViewItem.third) { + NoFilesView(state.noFilesViewItem, onDownloadButtonClick) } else { - BookItemList(state, onClick, onLongClick, onMultiSelect) + BookItemList( + state.fileSelectListState, + onClick, + onLongClick, + onMultiSelect, + lazyListState + ) } SelectFileButton( fabButtonClick, Modifier .align(Alignment.BottomEnd) - .padding(end = SIXTEEN_DP, bottom = FAB_ICON_BOTTOM_MARGIN) + .padding(end = SIXTEEN_DP, bottom = TEN_DP) ) } } @@ -122,8 +140,9 @@ private fun BookItemList( onClick: ((BookOnDisk) -> Unit)? = null, onLongClick: ((BookOnDisk) -> Unit)? = null, onMultiSelect: ((BookOnDisk) -> Unit)? = null, + lazyListState: LazyListState, ) { - LazyColumn(modifier = Modifier.fillMaxSize()) { + LazyColumn(modifier = Modifier.fillMaxSize(), state = lazyListState) { itemsIndexed(state.bookOnDiskListItems) { index, bookItem -> when (bookItem) { is BooksOnDiskListItem.LanguageItem -> { @@ -179,32 +198,3 @@ fun NoFilesView( KiwixButton(noFilesViewItem.second, onDownloadButtonClick) } } - -// @Preview -// @Preview(name = "NightMode", uiMode = Configuration.UI_MODE_NIGHT_YES) -// @Composable -// fun PreviewLocalLibrary() { -// LocalLibraryScreen( -// state = FileSelectListState(listOf(), SelectionMode.NORMAL), -// snackBarHostState = SnackbarHostState(), -// actionMenuItems = listOf( -// ActionMenuItem( -// IconItem.Drawable(org.kiwix.kiwixmobile.R.drawable.ic_baseline_mobile_screen_share_24px), -// R.string.get_content_from_nearby_device, -// { }, -// isEnabled = true, -// testingTag = DELETE_MENU_BUTTON_TESTING_TAG -// ) -// ), -// onRefresh = {}, -// fabButtonClick = {}, -// swipeRefreshItem = false to true, -// noFilesViewItem = Triple( -// stringResource(R.string.no_files_here), -// stringResource(R.string.download_books), -// {} -// ) -// ) { -// NavigationIcon(IconItem.Vector(Icons.Filled.Menu), {}) -// } -// } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreenState.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreenState.kt new file mode 100644 index 000000000..f79ecc3ba --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreenState.kt @@ -0,0 +1,73 @@ +/* + * Kiwix Android + * Copyright (c) 2025 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.nav.destination.library.local + +import androidx.compose.material3.SnackbarHostState +import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem +import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState + +/** + * Represents the UI state for the Local Library Screen. + * + * This data class encapsulates all UI-related states in a single object, + * reducing complexity in the Fragment. + */ +data class LocalLibraryScreenState( + /** + * Manages the file selection list state. + */ + val fileSelectListState: FileSelectListState, + /** + * Handles snack bar messages and displays. + */ + val snackBarHostState: SnackbarHostState, + /** + * Controls the visibility and behavior of the "Pull to refresh" animation. + * + * A [Pair] containing: + * - [Boolean]: The first boolean triggers/hides the "pull to refresh" animation. + * - [Boolean]: The second boolean enables/disables the "pull to refresh" gesture. + */ + val swipeRefreshItem: Pair, + /** + * Represents the scanning progress state. + * + * A [Pair] containing: + * - [Boolean]: Whether scanning is in progress. + * - [Int]: The progress percentage of the scan. + */ + val scanningProgressItem: Pair, + /** + * Controls the visibility of the "No files here" message and the "Download books" button. + * + * A [Triple] containing: + * - [String]: The title text displayed when no files are available. + * - [String]: The label for the download button. + * - [Boolean]: Whether to show or hide this view. + */ + val noFilesViewItem: Triple, + /** + * Represents a list of action menu items available in the screen's top app bar. + */ + val actionMenuItems: List, + /** + * Stores the height of the bottom navigation bar in pixels. + */ + val bottomNavigationHeight: Int +)