mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-09 07:16:04 -04:00
Created LocalLibraryScreenState
to encapsulate all UI-related states, reducing state management's complexity.
This commit is contained in:
parent
5b507d8f3d
commit
80fbf74d9a
@ -40,9 +40,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
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.INTERNAL_SELECT_POSITION
|
||||||
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
|
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
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.TAG_KIWIX
|
||||||
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
|
import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower
|
||||||
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
|
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.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
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
|
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.main.KiwixMainActivity
|
||||||
import org.kiwix.kiwixmobile.nav.destination.library.CopyMoveFileHandler
|
import org.kiwix.kiwixmobile.nav.destination.library.CopyMoveFileHandler
|
||||||
import org.kiwix.kiwixmobile.zimManager.MAX_PROGRESS
|
import org.kiwix.kiwixmobile.zimManager.MAX_PROGRESS
|
||||||
@ -134,34 +128,19 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
|
|
||||||
private var actionMode: ActionMode? = null
|
private var actionMode: ActionMode? = null
|
||||||
private val disposable = CompositeDisposable()
|
private val disposable = CompositeDisposable()
|
||||||
private var fragmentDestinationLibraryBinding: FragmentDestinationLibraryBinding? = null
|
|
||||||
private var permissionDeniedLayoutShowing = false
|
private var permissionDeniedLayoutShowing = false
|
||||||
private var zimFileUri: Uri? = null
|
private var zimFileUri: Uri? = null
|
||||||
private lateinit var snackBarHostState: SnackbarHostState
|
val libraryScreenState = mutableStateOf(
|
||||||
private var fileSelectListState = mutableStateOf(FileSelectListState(emptyList()))
|
LocalLibraryScreenState(
|
||||||
|
fileSelectListState = FileSelectListState(emptyList()),
|
||||||
/**
|
snackBarHostState = SnackbarHostState(),
|
||||||
* This is a Triple which is responsible for showing and hiding the "No file here",
|
swipeRefreshItem = Pair(false, true),
|
||||||
* and "Download books" button.
|
scanningProgressItem = Pair(false, ZERO),
|
||||||
*
|
noFilesViewItem = Triple("", "", false),
|
||||||
* A [Triple] containing:
|
actionMenuItems = listOf(),
|
||||||
* - [String]: The title text displayed when no files are available.
|
bottomNavigationHeight = ZERO
|
||||||
* - [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))
|
|
||||||
|
|
||||||
private val zimManageViewModel by lazy {
|
private val zimManageViewModel by lazy {
|
||||||
requireActivity().viewModel<ZimManageViewModel>(viewModelFactory)
|
requireActivity().viewModel<ZimManageViewModel>(viewModelFactory)
|
||||||
@ -174,10 +153,12 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
val isGranted = permissionResult.values.all { it }
|
val isGranted = permissionResult.values.all { it }
|
||||||
val isPermanentlyDenied = readStorageHasBeenPermanentlyDenied(isGranted)
|
val isPermanentlyDenied = readStorageHasBeenPermanentlyDenied(isGranted)
|
||||||
permissionDeniedLayoutShowing = isPermanentlyDenied
|
permissionDeniedLayoutShowing = isPermanentlyDenied
|
||||||
noFilesViewItem.value = Triple(
|
updateLibraryScreenState(
|
||||||
requireActivity().resources.getString(string.grant_read_storage_permission),
|
noFilesViewItem = Triple(
|
||||||
requireActivity().resources.getString(string.go_to_settings_label),
|
requireActivity().resources.getString(string.grant_read_storage_permission),
|
||||||
isPermanentlyDenied
|
requireActivity().resources.getString(string.go_to_settings_label),
|
||||||
|
isPermanentlyDenied
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,20 +176,18 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
|
|
||||||
val composeView = ComposeView(requireContext()).apply {
|
val composeView = ComposeView(requireContext()).apply {
|
||||||
setContent {
|
setContent {
|
||||||
snackBarHostState = remember { SnackbarHostState() }
|
updateLibraryScreenState(
|
||||||
|
bottomNavigationHeight = getBottomNavigationHeight(),
|
||||||
|
actionMenuItems = actionMenuItems()
|
||||||
|
)
|
||||||
LocalLibraryScreen(
|
LocalLibraryScreen(
|
||||||
state = fileSelectListState.value,
|
state = libraryScreenState.value,
|
||||||
snackBarHostState = snackBarHostState,
|
|
||||||
fabButtonClick = { filePickerButtonClick() },
|
fabButtonClick = { filePickerButtonClick() },
|
||||||
actionMenuItems = actionMenuItems(),
|
|
||||||
onClick = { onBookItemClick(it) },
|
onClick = { onBookItemClick(it) },
|
||||||
onLongClick = { onBookItemLongClick(it) },
|
onLongClick = { onBookItemLongClick(it) },
|
||||||
onMultiSelect = { offerAction(RequestSelect(it)) },
|
onMultiSelect = { offerAction(RequestSelect(it)) },
|
||||||
onRefresh = { onSwipeRefresh() },
|
onRefresh = { onSwipeRefresh() },
|
||||||
swipeRefreshItem = swipeRefreshItem.value,
|
|
||||||
noFilesViewItem = noFilesViewItem.value,
|
|
||||||
onDownloadButtonClick = { downloadBookButtonClick() },
|
onDownloadButtonClick = { downloadBookButtonClick() },
|
||||||
scanningProgressItem = scanningProgressItem.value
|
|
||||||
) {
|
) {
|
||||||
NavigationIcon(
|
NavigationIcon(
|
||||||
iconItem = IconItem.Vector(Icons.Filled.Menu),
|
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<Boolean, Boolean>? = null,
|
||||||
|
scanningProgressItem: Pair<Boolean, Int>? = null,
|
||||||
|
noFilesViewItem: Triple<String, String, Boolean>? = null,
|
||||||
|
actionMenuItems: List<ActionMenuItem>? = 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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
copyMoveFileHandler?.apply {
|
copyMoveFileHandler?.apply {
|
||||||
@ -270,38 +271,20 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
.also {
|
.also {
|
||||||
coreMainActivity.navHostContainer
|
coreMainActivity.navHostContainer
|
||||||
.setBottomMarginToFragmentContainerView(0)
|
.setBottomMarginToFragmentContainerView(0)
|
||||||
|
|
||||||
getBottomNavigationView()?.let { bottomNavigationView ->
|
|
||||||
setBottomMarginToSwipeRefreshLayout(bottomNavigationView.measuredHeight)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
disposable.add(sideEffects())
|
disposable.add(sideEffects())
|
||||||
disposable.add(fileSelectActions())
|
disposable.add(fileSelectActions())
|
||||||
zimManageViewModel.deviceListScanningProgress.observe(viewLifecycleOwner) {
|
zimManageViewModel.deviceListScanningProgress.observe(viewLifecycleOwner) {
|
||||||
// hide this progress bar when scanning is complete.
|
updateLibraryScreenState(
|
||||||
scanningProgressItem.value = Pair(it != MAX_PROGRESS, it)
|
// hide this progress bar when scanning is complete.
|
||||||
// enable if the previous scanning is completes.
|
scanningProgressItem = Pair(it != MAX_PROGRESS, it),
|
||||||
swipeRefreshItem.value = Pair(false, it == MAX_PROGRESS)
|
// enable if the previous scanning is completes.
|
||||||
|
swipeRefreshItem = Pair(false, it == MAX_PROGRESS)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (savedInstanceState != null && savedInstanceState.getBoolean(WAS_IN_ACTION_MODE)) {
|
if (savedInstanceState != null && savedInstanceState.getBoolean(WAS_IN_ACTION_MODE)) {
|
||||||
zimManageViewModel.fileSelectActions.offer(FileSelectActions.RestartActionMode)
|
zimManageViewModel.fileSelectActions.offer(FileSelectActions.RestartActionMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fragmentDestinationLibraryBinding?.zimfilelist?.addOnScrollListener(
|
|
||||||
SimpleRecyclerViewScrollListener { _, newState ->
|
|
||||||
when (newState) {
|
|
||||||
SCROLL_DOWN -> {
|
|
||||||
setBottomMarginToSwipeRefreshLayout(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
SCROLL_UP -> {
|
|
||||||
getBottomNavigationView()?.let {
|
|
||||||
setBottomMarginToSwipeRefreshLayout(it.measuredHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
showCopyMoveDialogForOpenedZimFileFromStorage()
|
showCopyMoveDialogForOpenedZimFileFromStorage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,22 +308,24 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
private fun onSwipeRefresh() {
|
private fun onSwipeRefresh() {
|
||||||
if (permissionDeniedLayoutShowing) {
|
if (permissionDeniedLayoutShowing) {
|
||||||
// When permission denied layout is showing hide the "Swipe refresh".
|
// When permission denied layout is showing hide the "Swipe refresh".
|
||||||
swipeRefreshItem.value = false to true
|
updateLibraryScreenState(swipeRefreshItem = false to true)
|
||||||
} else {
|
} else {
|
||||||
if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) {
|
if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) {
|
||||||
showManageExternalStoragePermissionDialog()
|
showManageExternalStoragePermissionDialog()
|
||||||
// Set loading to false since the dialog is currently being displayed.
|
// Set loading to false since the dialog is currently being displayed.
|
||||||
// If the user clicks on "No" in the permission dialog,
|
// If the user clicks on "No" in the permission dialog,
|
||||||
// the loading icon remains visible infinitely.
|
// the loading icon remains visible infinitely.
|
||||||
swipeRefreshItem.value = false to true
|
updateLibraryScreenState(swipeRefreshItem = false to true)
|
||||||
} else {
|
} else {
|
||||||
// hide the swipe refreshing because now we are showing the ContentLoadingProgressBar
|
// hide the swipe refreshing because now we are showing the ContentLoadingProgressBar
|
||||||
// to show the progress of how many files are scanned.
|
// to show the progress of how many files are scanned.
|
||||||
// disable the swipe refresh layout until the ongoing scanning will not complete
|
// disable the swipe refresh layout until the ongoing scanning will not complete
|
||||||
// to avoid multiple scanning.
|
// to avoid multiple scanning.
|
||||||
swipeRefreshItem.value = false to false
|
updateLibraryScreenState(
|
||||||
// Show the progress Bar.
|
swipeRefreshItem = false to false,
|
||||||
scanningProgressItem.value = true to ZERO
|
// Show the progress Bar.
|
||||||
|
scanningProgressItem = true to ZERO
|
||||||
|
)
|
||||||
requestFileSystemCheck()
|
requestFileSystemCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,13 +345,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
private fun getBottomNavigationView() =
|
private fun getBottomNavigationView() =
|
||||||
requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view)
|
requireActivity().findViewById<BottomNavigationView>(R.id.bottom_nav_view)
|
||||||
|
|
||||||
private fun setBottomMarginToSwipeRefreshLayout(marginBottom: Int) {
|
private fun getBottomNavigationHeight() = getBottomNavigationView().measuredHeight
|
||||||
fragmentDestinationLibraryBinding?.zimSwiperefresh?.apply {
|
|
||||||
val params = layoutParams as CoordinatorLayout.LayoutParams?
|
|
||||||
params?.bottomMargin = marginBottom
|
|
||||||
requestLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filePickerButtonClick() {
|
private fun filePickerButtonClick() {
|
||||||
if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) {
|
if (!requireActivity().isManageExternalStoragePermissionGranted(sharedPreferenceUtil)) {
|
||||||
@ -510,10 +489,12 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
) {
|
) {
|
||||||
checkPermissions()
|
checkPermissions()
|
||||||
} else if (!permissionDeniedLayoutShowing) {
|
} else if (!permissionDeniedLayoutShowing) {
|
||||||
noFilesViewItem.value = Triple(
|
updateLibraryScreenState(
|
||||||
requireActivity().resources.getString(string.no_files_here),
|
noFilesViewItem = Triple(
|
||||||
requireActivity().resources.getString(string.download_books),
|
requireActivity().resources.getString(string.no_files_here),
|
||||||
false
|
requireActivity().resources.getString(string.download_books),
|
||||||
|
false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,7 +521,11 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
val effectResult = it.invokeWith(requireActivity() as AppCompatActivity)
|
val effectResult = it.invokeWith(requireActivity() as AppCompatActivity)
|
||||||
if (effectResult is ActionMode) {
|
if (effectResult is ActionMode) {
|
||||||
actionMode = effectResult
|
actionMode = effectResult
|
||||||
fileSelectListState.value.selectedBooks.size.let(::setActionModeTitle)
|
libraryScreenState
|
||||||
|
.value
|
||||||
|
.fileSelectListState
|
||||||
|
.selectedBooks
|
||||||
|
.size.let(::setActionModeTitle)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Throwable::printStackTrace
|
Throwable::printStackTrace
|
||||||
@ -568,20 +553,21 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
// Force recomposition by first setting an empty list before assigning the updated list.
|
// 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,
|
// This is necessary because modifying an object's property doesn't trigger recomposition,
|
||||||
// as Compose still considers the list unchanged.
|
// as Compose still considers the list unchanged.
|
||||||
fileSelectListState.value = FileSelectListState(emptyList())
|
updateLibraryScreenState(
|
||||||
fileSelectListState.value = state
|
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)) {
|
if (state.bookOnDiskListItems.none(BooksOnDiskListItem::isSelected)) {
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
actionMode = null
|
actionMode = null
|
||||||
}
|
}
|
||||||
setActionModeTitle(state.selectedBooks.size)
|
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) {
|
private fun setActionModeTitle(selectedBookCount: Int) {
|
||||||
@ -679,7 +665,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showStorageSelectionSnackBar(message: String) {
|
private fun showStorageSelectionSnackBar(message: String) {
|
||||||
snackBarHostState.snack(
|
libraryScreenState.value.snackBarHostState.snack(
|
||||||
message = message,
|
message = message,
|
||||||
actionLabel = getString(string.download_change_storage),
|
actionLabel = getString(string.download_change_storage),
|
||||||
lifecycleScope = lifecycleScope,
|
lifecycleScope = lifecycleScope,
|
||||||
|
@ -26,35 +26,42 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import org.kiwix.kiwixmobile.R.string
|
import org.kiwix.kiwixmobile.R.string
|
||||||
import org.kiwix.kiwixmobile.core.R
|
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.ContentLoadingProgressBar
|
||||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
|
||||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
|
import org.kiwix.kiwixmobile.core.ui.components.KiwixButton
|
||||||
import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost
|
import org.kiwix.kiwixmobile.core.ui.components.KiwixSnackbarHost
|
||||||
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
|
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
|
||||||
import org.kiwix.kiwixmobile.core.ui.components.SwipeRefreshLayout
|
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.Black
|
||||||
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
|
||||||
import org.kiwix.kiwixmobile.core.ui.theme.White
|
import org.kiwix.kiwixmobile.core.ui.theme.White
|
||||||
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
|
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.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
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
|
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
|
||||||
import org.kiwix.kiwixmobile.ui.BookItem
|
import org.kiwix.kiwixmobile.ui.BookItem
|
||||||
@ -62,54 +69,65 @@ import org.kiwix.kiwixmobile.ui.ZimFilesLanguageHeader
|
|||||||
import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState
|
import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Suppress("ComposableLambdaParameterNaming", "LongParameterList")
|
@Suppress("ComposableLambdaParameterNaming")
|
||||||
@Composable
|
@Composable
|
||||||
fun LocalLibraryScreen(
|
fun LocalLibraryScreen(
|
||||||
state: FileSelectListState,
|
state: LocalLibraryScreenState,
|
||||||
snackBarHostState: SnackbarHostState,
|
|
||||||
swipeRefreshItem: Pair<Boolean, Boolean>,
|
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
scanningProgressItem: Pair<Boolean, Int>,
|
|
||||||
noFilesViewItem: Triple<String, String, Boolean>,
|
|
||||||
onDownloadButtonClick: () -> Unit,
|
onDownloadButtonClick: () -> Unit,
|
||||||
fabButtonClick: () -> Unit,
|
fabButtonClick: () -> Unit,
|
||||||
actionMenuItems: List<ActionMenuItem>,
|
|
||||||
onClick: ((BookOnDisk) -> Unit)? = null,
|
onClick: ((BookOnDisk) -> Unit)? = null,
|
||||||
onLongClick: ((BookOnDisk) -> Unit)? = null,
|
onLongClick: ((BookOnDisk) -> Unit)? = null,
|
||||||
onMultiSelect: ((BookOnDisk) -> Unit)? = null,
|
onMultiSelect: ((BookOnDisk) -> Unit)? = null,
|
||||||
navigationIcon: @Composable () -> Unit
|
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 {
|
KiwixTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = { KiwixSnackbarHost(snackbarHostState = snackBarHostState) },
|
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
|
||||||
topBar = { KiwixAppBar(R.string.library, navigationIcon, actionMenuItems) },
|
topBar = { KiwixAppBar(R.string.library, navigationIcon, state.actionMenuItems) },
|
||||||
modifier = Modifier.systemBarsPadding()
|
modifier = Modifier.systemBarsPadding()
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
SwipeRefreshLayout(
|
SwipeRefreshLayout(
|
||||||
isRefreshing = swipeRefreshItem.first,
|
isRefreshing = state.swipeRefreshItem.first,
|
||||||
isEnabled = swipeRefreshItem.second,
|
isEnabled = state.swipeRefreshItem.second,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
|
.padding(bottom = bottomNavHeight.value)
|
||||||
) {
|
) {
|
||||||
if (scanningProgressItem.first) {
|
if (state.scanningProgressItem.first) {
|
||||||
ContentLoadingProgressBar(
|
ContentLoadingProgressBar(
|
||||||
progressBarStyle = ProgressBarStyle.HORIZONTAL,
|
progressBarStyle = ProgressBarStyle.HORIZONTAL,
|
||||||
progress = scanningProgressItem.second
|
progress = state.scanningProgressItem.second
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (noFilesViewItem.third) {
|
if (state.noFilesViewItem.third) {
|
||||||
NoFilesView(noFilesViewItem, onDownloadButtonClick)
|
NoFilesView(state.noFilesViewItem, onDownloadButtonClick)
|
||||||
} else {
|
} else {
|
||||||
BookItemList(state, onClick, onLongClick, onMultiSelect)
|
BookItemList(
|
||||||
|
state.fileSelectListState,
|
||||||
|
onClick,
|
||||||
|
onLongClick,
|
||||||
|
onMultiSelect,
|
||||||
|
lazyListState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectFileButton(
|
SelectFileButton(
|
||||||
fabButtonClick,
|
fabButtonClick,
|
||||||
Modifier
|
Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.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,
|
onClick: ((BookOnDisk) -> Unit)? = null,
|
||||||
onLongClick: ((BookOnDisk) -> Unit)? = null,
|
onLongClick: ((BookOnDisk) -> Unit)? = null,
|
||||||
onMultiSelect: ((BookOnDisk) -> Unit)? = null,
|
onMultiSelect: ((BookOnDisk) -> Unit)? = null,
|
||||||
|
lazyListState: LazyListState,
|
||||||
) {
|
) {
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
LazyColumn(modifier = Modifier.fillMaxSize(), state = lazyListState) {
|
||||||
itemsIndexed(state.bookOnDiskListItems) { index, bookItem ->
|
itemsIndexed(state.bookOnDiskListItems) { index, bookItem ->
|
||||||
when (bookItem) {
|
when (bookItem) {
|
||||||
is BooksOnDiskListItem.LanguageItem -> {
|
is BooksOnDiskListItem.LanguageItem -> {
|
||||||
@ -179,32 +198,3 @@ fun NoFilesView(
|
|||||||
KiwixButton(noFilesViewItem.second, onDownloadButtonClick)
|
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), {})
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Kiwix Android
|
||||||
|
* Copyright (c) 2025 Kiwix <android.kiwix.org>
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Boolean, Boolean>,
|
||||||
|
/**
|
||||||
|
* Represents the scanning progress state.
|
||||||
|
*
|
||||||
|
* A [Pair] containing:
|
||||||
|
* - [Boolean]: Whether scanning is in progress.
|
||||||
|
* - [Int]: The progress percentage of the scan.
|
||||||
|
*/
|
||||||
|
val scanningProgressItem: Pair<Boolean, Int>,
|
||||||
|
/**
|
||||||
|
* 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<String, String, Boolean>,
|
||||||
|
/**
|
||||||
|
* Represents a list of action menu items available in the screen's top app bar.
|
||||||
|
*/
|
||||||
|
val actionMenuItems: List<ActionMenuItem>,
|
||||||
|
/**
|
||||||
|
* Stores the height of the bottom navigation bar in pixels.
|
||||||
|
*/
|
||||||
|
val bottomNavigationHeight: Int
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user