diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/components/KiwixActivityComponent.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/components/KiwixActivityComponent.kt index df503ac30..8cd498c60 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/components/KiwixActivityComponent.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/components/KiwixActivityComponent.kt @@ -30,7 +30,7 @@ import org.kiwix.kiwixmobile.intro.IntroModule import org.kiwix.kiwixmobile.language.LanguageFragment import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferFragment import org.kiwix.kiwixmobile.main.KiwixMainActivity -import org.kiwix.kiwixmobile.nav.destination.library.LocalLibraryFragment +import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragment import org.kiwix.kiwixmobile.nav.destination.library.OnlineLibraryFragment import org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragment import org.kiwix.kiwixmobile.settings.KiwixSettingsFragment diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt similarity index 89% rename from app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt rename to app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt index b12f2d157..300477bc6 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryFragment.kt @@ -1,6 +1,6 @@ /* * Kiwix Android - * Copyright (c) 2020 Kiwix + * 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 @@ -16,7 +16,7 @@ * */ -package org.kiwix.kiwixmobile.nav.destination.library +package org.kiwix.kiwixmobile.nav.destination.library.local import android.Manifest import android.app.Activity.RESULT_OK @@ -30,8 +30,6 @@ import android.os.Environment import android.util.Log import android.view.LayoutInflater import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.Toast import androidx.activity.result.ActivityResultLauncher @@ -67,6 +65,7 @@ import org.kiwix.kiwixmobile.cachedComponent import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseFragment +import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isManageExternalStoragePermissionGranted import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel @@ -100,6 +99,7 @@ import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDis 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 import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions @@ -151,6 +151,18 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal */ 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 { requireActivity().viewModel(viewModelFactory) } @@ -159,20 +171,14 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissionResult -> - val isGranted = - permissionResult.entries.all( - Map.Entry::value - ) - if (readStorageHasBeenPermanentlyDenied(isGranted)) { - permissionDeniedLayoutShowing = true - noFilesViewItem.value = Triple( - requireActivity().resources.getString(string.grant_read_storage_permission), - requireActivity().resources.getString(string.go_to_settings_label), - true - ) - } else if (isGranted) { - permissionDeniedLayoutShowing = false - } + 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 + ) } override fun inject(baseActivity: BaseActivity) { @@ -198,10 +204,11 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal onClick = { onBookItemClick(it) }, onLongClick = { onBookItemLongClick(it) }, onMultiSelect = { offerAction(RequestSelect(it)) }, - onRefresh = {}, - swipeRefreshItem = true to true, + onRefresh = { onSwipeRefresh() }, + swipeRefreshItem = swipeRefreshItem.value, noFilesViewItem = noFilesViewItem.value, - onDownloadButtonClick = { downloadBookButtonClick() } + onDownloadButtonClick = { downloadBookButtonClick() }, + scanningProgressItem = scanningProgressItem.value ) { NavigationIcon( iconItem = IconItem.Vector(Icons.Filled.Menu), @@ -255,7 +262,6 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setUpSwipeRefreshLayout() copyMoveFileHandler?.apply { setFileCopyMoveCallback(this@LocalLibraryFragment) setLifeCycleScope(lifecycleScope) @@ -272,13 +278,10 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal disposable.add(sideEffects()) disposable.add(fileSelectActions()) zimManageViewModel.deviceListScanningProgress.observe(viewLifecycleOwner) { - fragmentDestinationLibraryBinding?.scanningProgressView?.apply { - progress = it - // hide this progress bar when scanning is complete. - visibility = if (it == MAX_PROGRESS) GONE else VISIBLE - // enable if the previous scanning is completes. - fragmentDestinationLibraryBinding?.zimSwiperefresh?.isEnabled = it == MAX_PROGRESS - } + // 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) } if (savedInstanceState != null && savedInstanceState.getBoolean(WAS_IN_ACTION_MODE)) { zimManageViewModel.fileSelectActions.offer(FileSelectActions.RestartActionMode) @@ -319,32 +322,26 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal requireArguments().clear() } - private fun setUpSwipeRefreshLayout() { - fragmentDestinationLibraryBinding?.zimSwiperefresh?.setOnRefreshListener { - if (permissionDeniedLayoutShowing) { - fragmentDestinationLibraryBinding?.zimSwiperefresh?.isRefreshing = false + private fun onSwipeRefresh() { + if (permissionDeniedLayoutShowing) { + // When permission denied layout is showing hide the "Swipe refresh". + swipeRefreshItem.value = 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 } 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. - fragmentDestinationLibraryBinding?.zimSwiperefresh?.isRefreshing = false - } else { - fragmentDestinationLibraryBinding?.zimSwiperefresh?.apply { - // hide the swipe refreshing because now we are showing the ContentLoadingProgressBar - // to show the progress of how many files are scanned. - isRefreshing = false - // disable the swipe refresh layout until the ongoing scanning will not complete - // to avoid multiple scanning. - isEnabled = false - } - fragmentDestinationLibraryBinding?.scanningProgressView?.apply { - visibility = VISIBLE - progress = 0 - } - requestFileSystemCheck() - } + // 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 + requestFileSystemCheck() } } } @@ -394,7 +391,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal } try { fileSelectLauncher.launch(Intent.createChooser(intent, "Select a zim file")) - } catch (ex: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { activity.toast(resources.getString(R.string.no_app_found_to_open), Toast.LENGTH_SHORT) } } @@ -450,7 +447,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal } private fun isValidZimFile(fileName: String): Boolean = - FileUtils.isValidZimFile(fileName) || FileUtils.isSplittedZimFile(fileName) + FileUtils.isValidZimFile(fileName) || isSplittedZimFile(fileName) private suspend fun getZimFileFromUri( uri: Uri @@ -513,7 +510,11 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal ) { checkPermissions() } else if (!permissionDeniedLayoutShowing) { - fragmentDestinationLibraryBinding?.zimfilelist?.visibility = VISIBLE + noFilesViewItem.value = Triple( + requireActivity().resources.getString(string.no_files_here), + requireActivity().resources.getString(string.download_books), + false + ) } } @@ -521,8 +522,6 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal super.onDestroyView() mainRepositoryActions.dispose() actionMode = null - fragmentDestinationLibraryBinding?.zimfilelist?.adapter = null - fragmentDestinationLibraryBinding = null disposable.clear() storagePermissionLauncher?.unregister() storagePermissionLauncher = null @@ -680,11 +679,11 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal } private fun showStorageSelectionSnackBar(message: String) { - fragmentDestinationLibraryBinding?.zimfilelist?.snack( - message, - requireActivity().findViewById(R.id.bottom_nav_view), - string.download_change_storage, - { + snackBarHostState.snack( + message = message, + actionLabel = getString(string.download_change_storage), + lifecycleScope = lifecycleScope, + actionClick = { lifecycleScope.launch { showStorageSelectDialog((requireActivity() as KiwixMainActivity).getStorageDeviceList()) } @@ -738,7 +737,7 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal ) { permissionResult -> val isGranted = permissionResult.entries.all( - Map.Entry::value + Map.Entry::value ) if (isGranted) { zimFileUri?.let { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt similarity index 91% rename from app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryScreen.kt rename to app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt index 9ae9508e4..31db0b7d3 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/local/LocalLibraryScreen.kt @@ -16,10 +16,9 @@ * */ -package org.kiwix.kiwixmobile.nav.destination.library +package org.kiwix.kiwixmobile.nav.destination.library.local import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -43,9 +42,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import org.kiwix.kiwixmobile.R.string import org.kiwix.kiwixmobile.core.R +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 @@ -60,13 +62,14 @@ import org.kiwix.kiwixmobile.ui.ZimFilesLanguageHeader import org.kiwix.kiwixmobile.zimManager.fileselectView.FileSelectListState @OptIn(ExperimentalMaterial3Api::class) -@Suppress("ComposableLambdaParameterNaming", "LongParameterList", "UnusedParameter") +@Suppress("ComposableLambdaParameterNaming", "LongParameterList") @Composable fun LocalLibraryScreen( state: FileSelectListState, snackBarHostState: SnackbarHostState, swipeRefreshItem: Pair, onRefresh: () -> Unit, + scanningProgressItem: Pair, noFilesViewItem: Triple, onDownloadButtonClick: () -> Unit, fabButtonClick: () -> Unit, @@ -76,28 +79,26 @@ fun LocalLibraryScreen( onMultiSelect: ((BookOnDisk) -> Unit)? = null, navigationIcon: @Composable () -> Unit ) { - // val swipeRefreshState = rememberPullToRefreshState() KiwixTheme { Scaffold( snackbarHost = { KiwixSnackbarHost(snackbarHostState = snackBarHostState) }, topBar = { KiwixAppBar(R.string.library, navigationIcon, actionMenuItems) }, modifier = Modifier.systemBarsPadding() ) { contentPadding -> - Box( + SwipeRefreshLayout( + isRefreshing = swipeRefreshItem.first, + isEnabled = swipeRefreshItem.second, + onRefresh = onRefresh, modifier = Modifier .fillMaxSize() .padding(contentPadding) - // .pullToRefresh( - // isRefreshing = swipeRefreshItem.first, - // state = swipeRefreshState, - // enabled = swipeRefreshItem.second, - // onRefresh = { onRefresh } - // ) - // .pullToRefreshIndicator( - // state = swipeRefreshState, - // isRefreshing = swipeRefreshItem.first - // ) ) { + if (scanningProgressItem.first) { + ContentLoadingProgressBar( + progressBarStyle = ProgressBarStyle.HORIZONTAL, + progress = scanningProgressItem.second + ) + } if (noFilesViewItem.third) { NoFilesView(noFilesViewItem, onDownloadButtonClick) } else { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/NavigateToDownloads.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/NavigateToDownloads.kt index 4014eafeb..0a5338d97 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/NavigateToDownloads.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/NavigateToDownloads.kt @@ -21,7 +21,7 @@ package org.kiwix.kiwixmobile.zimManager.fileselectView.effects import androidx.appcompat.app.AppCompatActivity import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate -import org.kiwix.kiwixmobile.nav.destination.library.LocalLibraryFragmentDirections +import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections object NavigateToDownloads : SideEffect { override fun invokeWith(activity: AppCompatActivity) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/OpenFileWithNavigation.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/OpenFileWithNavigation.kt index 574410fa4..63788f45a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/OpenFileWithNavigation.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/OpenFileWithNavigation.kt @@ -25,11 +25,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.SideEffect -import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.main.KiwixMainActivity -import org.kiwix.kiwixmobile.nav.destination.library.LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader +import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader @Suppress("InjectDispatcher") data class OpenFileWithNavigation(private val bookOnDisk: BooksOnDiskListItem.BookOnDisk) : diff --git a/app/src/main/res/navigation/kiwix_nav_graph.xml b/app/src/main/res/navigation/kiwix_nav_graph.xml index fd8717d0b..60f5534e0 100644 --- a/app/src/main/res/navigation/kiwix_nav_graph.xml +++ b/app/src/main/res/navigation/kiwix_nav_graph.xml @@ -65,7 +65,7 @@ + * 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.core.ui.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.ProgressIndicatorDefaults.drawStopIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.unit.dp +import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO +import org.kiwix.kiwixmobile.core.ui.theme.DenimBlue400 +import org.kiwix.kiwixmobile.core.ui.theme.MineShaftGray350 + +@Composable +fun ContentLoadingProgressBar( + modifier: Modifier = Modifier, + progressBarStyle: ProgressBarStyle = ProgressBarStyle.CIRCLE, + progress: Int = ZERO, + progressBarColor: Color = DenimBlue400, + progressBarTrackColor: Color = MineShaftGray350 +) { + when (progressBarStyle) { + ProgressBarStyle.CIRCLE -> { + CircularProgressIndicator( + modifier = modifier, + color = progressBarColor, + trackColor = progressBarTrackColor + ) + } + + ProgressBarStyle.HORIZONTAL -> { + LinearProgressIndicator( + modifier = modifier.fillMaxWidth(), + progress = { progress.toFloat() / HUNDERED }, + color = progressBarColor, + trackColor = progressBarTrackColor, + gapSize = ZERO.dp, + strokeCap = StrokeCap.Butt, + drawStopIndicator = { + drawStopIndicator( + drawScope = this, + stopSize = ZERO.dp, + color = progressBarTrackColor, + strokeCap = StrokeCap.Butt + ) + } + ) + } + } +} + +enum class ProgressBarStyle { + HORIZONTAL, + CIRCLE +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/SwipeRefreshLayout.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/SwipeRefreshLayout.kt new file mode 100644 index 000000000..c9da3d0c1 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/ui/components/SwipeRefreshLayout.kt @@ -0,0 +1,66 @@ +/* + * 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.core.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator +import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.pullToRefresh +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.kiwix.kiwixmobile.core.ui.theme.Black +import org.kiwix.kiwixmobile.core.ui.theme.White + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SwipeRefreshLayout( + isRefreshing: Boolean, + isEnabled: Boolean, + onRefresh: () -> Unit, + modifier: Modifier = Modifier, + state: PullToRefreshState = rememberPullToRefreshState(), + contentAlignment: Alignment = Alignment.TopStart, + indicator: @Composable BoxScope.() -> Unit = { + Indicator( + modifier = Modifier.align(Alignment.TopCenter), + isRefreshing = isRefreshing, + state = state, + containerColor = White, + color = Black + ) + }, + content: @Composable BoxScope.() -> Unit +) { + Box( + modifier.pullToRefresh( + state = state, + isRefreshing = isRefreshing, + onRefresh = onRefresh, + enabled = isEnabled + ), + contentAlignment = contentAlignment + ) { + content() + indicator() + } +}