diff --git a/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt b/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt index 0e6b9d34eb..fe9853ac1b 100644 --- a/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt +++ b/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt @@ -95,6 +95,8 @@ import au.com.shiftyjelly.pocketcasts.player.view.bookmark.BookmarksContainerFra import au.com.shiftyjelly.pocketcasts.player.view.dialog.MiniPlayerDialog import au.com.shiftyjelly.pocketcasts.player.view.video.VideoActivity import au.com.shiftyjelly.pocketcasts.podcasts.view.ProfileEpisodeListFragment +import au.com.shiftyjelly.pocketcasts.podcasts.view.ProfileEpisodeListFragment.Companion.CLEAN_UP +import au.com.shiftyjelly.pocketcasts.podcasts.view.ProfileEpisodeListFragment.Companion.OPTION_KEY import au.com.shiftyjelly.pocketcasts.podcasts.view.episode.EpisodeContainerFragment import au.com.shiftyjelly.pocketcasts.podcasts.view.podcast.PodcastFragment import au.com.shiftyjelly.pocketcasts.podcasts.view.podcasts.PodcastsFragment @@ -128,6 +130,7 @@ import au.com.shiftyjelly.pocketcasts.search.SearchFragment import au.com.shiftyjelly.pocketcasts.servers.ServerCallback import au.com.shiftyjelly.pocketcasts.servers.ServiceManager import au.com.shiftyjelly.pocketcasts.servers.discover.PodcastSearch +import au.com.shiftyjelly.pocketcasts.settings.ManualCleanupFragment import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingFlow import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingLauncher import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingUpgradeSource @@ -150,6 +153,8 @@ import au.com.shiftyjelly.pocketcasts.views.fragments.BaseFragment import au.com.shiftyjelly.pocketcasts.views.helper.HasBackstack import au.com.shiftyjelly.pocketcasts.views.helper.UiUtil import au.com.shiftyjelly.pocketcasts.views.helper.WarningsHelper +import au.com.shiftyjelly.pocketcasts.views.lowstorage.LowStorageBottomSheetListener +import au.com.shiftyjelly.pocketcasts.views.lowstorage.LowStorageLaunchBottomSheet import com.automattic.android.tracks.crashlogging.CrashLogging import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.snackbar.Snackbar @@ -190,6 +195,7 @@ class MainActivity : FragmentHostListener, PlayerBottomSheet.PlayerBottomSheetListener, SearchFragment.Listener, + LowStorageBottomSheetListener, OnboardingLauncher, CoroutineScope, NotificationPermissionChecker { @@ -730,6 +736,32 @@ class MainActivity : ) } + private fun setupLowStorageLaunchBottomSheet() { + val viewGroup = binding.modalBottomSheet + viewGroup.removeAllViews() + viewGroup.addView( + ComposeView(viewGroup.context).apply { + setContent { + AppTheme(theme.activeTheme) { + LowStorageLaunchBottomSheet( + parent = viewGroup, + shouldShow = true, + onManageDownloadsClick = { + analyticsTracker.track(AnalyticsEvent.DOWNLOADS_OPTIONS_MODAL_OPTION_TAPPED, mapOf(OPTION_KEY to CLEAN_UP)) + addFragment(ManualCleanupFragment.newInstance()) + }, + onExpanded = { + }, + onMaybeLaterClick = { + }, + totalDownloadSize = 324234, + ) + } + } + }, + ) + } + private fun showEndOfYearModal() { viewModel.updateStoriesModalShowState(true) launch(Dispatchers.Main) { @@ -1623,4 +1655,10 @@ class MainActivity : .setTextColor(ThemeColor.primaryText01(Theme.ThemeType.DARK)) .show() } + + override fun showModal() { + launch(Dispatchers.Main) { + setupLowStorageLaunchBottomSheet() + } + } } diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/ProfileEpisodeListFragment.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/ProfileEpisodeListFragment.kt index ca19d7c21f..8e2839e651 100644 --- a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/ProfileEpisodeListFragment.kt +++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/ProfileEpisodeListFragment.kt @@ -83,11 +83,11 @@ class ProfileEpisodeListFragment : BaseFragment(), Toolbar.OnMenuItemClickListen } companion object { + const val OPTION_KEY = "option" + const val CLEAN_UP = "clean_up" private const val SELECT_ALL_KEY = "select_all" - private const val OPTION_KEY = "option" private const val AUTO_DOWNLOAD_SETTINGS = "auto_download_settings" private const val STOP_ALL_DOWNLOADS = "stop_all_downloads" - private const val CLEAN_UP = "clean_up" private const val CLEAR_HISTORY = "clear_history" fun newInstance(mode: Mode): ProfileEpisodeListFragment { diff --git a/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/StorageSettingsFragment.kt b/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/StorageSettingsFragment.kt index e2281c95c0..33f2b868e2 100644 --- a/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/StorageSettingsFragment.kt +++ b/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/StorageSettingsFragment.kt @@ -1,6 +1,7 @@ package au.com.shiftyjelly.pocketcasts.settings import android.Manifest +import android.content.Context import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater @@ -23,6 +24,7 @@ import au.com.shiftyjelly.pocketcasts.settings.viewmodel.StorageSettingsViewMode import au.com.shiftyjelly.pocketcasts.ui.helper.FragmentHostListener import au.com.shiftyjelly.pocketcasts.utils.extensions.pxToDp import au.com.shiftyjelly.pocketcasts.views.fragments.BaseFragment +import au.com.shiftyjelly.pocketcasts.views.lowstorage.LowStorageBottomSheetListener import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlinx.coroutines.launch @@ -40,6 +42,18 @@ class StorageSettingsFragment : BaseFragment() { @Inject lateinit var settings: Settings + private var listener: LowStorageBottomSheetListener? = null + + override fun onAttach(context: Context) { + super.onAttach(context) + listener = context as? LowStorageBottomSheetListener + } + + override fun onDetach() { + super.onDetach() + listener = null + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -63,6 +77,7 @@ class StorageSettingsFragment : BaseFragment() { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + listener?.showModal() viewModel.permissionRequest.collect { permissionRequest -> if (permissionRequest == Manifest.permission.WRITE_EXTERNAL_STORAGE) { requestPermissionLauncher.launch(permissionRequest) diff --git a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/bottomsheet/ModalBottomSheet.kt b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/bottomsheet/ModalBottomSheet.kt index f3a17f2f4f..fdecee0264 100644 --- a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/bottomsheet/ModalBottomSheet.kt +++ b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/bottomsheet/ModalBottomSheet.kt @@ -3,7 +3,6 @@ package au.com.shiftyjelly.pocketcasts.compose.bottomsheet import android.view.ViewGroup import androidx.activity.compose.BackHandler import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue @@ -21,7 +20,6 @@ import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) @Composable fun ModalBottomSheet( parent: ViewGroup, @@ -77,14 +75,63 @@ fun ModalBottomSheet( } } -@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ModalBottomSheet( + parent: ViewGroup, + onExpanded: () -> Unit, + shouldShow: Boolean, + customSheetState: ModalBottomSheetState? = null, + customContent: @Composable () -> Unit, +) { + val sheetState = customSheetState ?: rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Hidden, + skipHalfExpanded = true, + ) + val coroutineScope = rememberCoroutineScope() + var isSheetShown by remember { mutableStateOf(false) } + + BackHandler(sheetState.isVisible) { + hideBottomSheet(coroutineScope, sheetState) + } + + ModalBottomSheetLayout( + sheetState = sheetState, + sheetContent = { + customContent() + }, + sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + scrimColor = Color.Black.copy(alpha = .25f), + content = {}, + ) + + LaunchedEffect(Unit) { + snapshotFlow { sheetState.currentValue } + .collect { + if (sheetState.currentValue == ModalBottomSheetValue.Expanded) { + onExpanded.invoke() + } else if (sheetState.currentValue == ModalBottomSheetValue.Hidden) { + if (isSheetShown) { + /* Remove bottom sheet from parent view when bottom sheet is hidden + on dismiss or back action for talkback to function properly. */ + parent.removeAllViews() + } else { + if (!sheetState.isVisible && shouldShow) { + /* Show bottom sheet when it is hidden on initial set up */ + displayBottomSheet(coroutineScope, sheetState) + isSheetShown = true + } + } + } + } + } +} + private fun hideBottomSheet(coroutineScope: CoroutineScope, sheetState: ModalBottomSheetState) { coroutineScope.launch { sheetState.hide() } } -@OptIn(ExperimentalMaterialApi::class) fun displayBottomSheet(coroutineScope: CoroutineScope, sheetState: ModalBottomSheetState) { coroutineScope.launch { sheetState.show() diff --git a/modules/services/views/src/main/java/au/com/shiftyjelly/pocketcasts/views/lowstorage/LowStorageDialog.kt b/modules/services/views/src/main/java/au/com/shiftyjelly/pocketcasts/views/lowstorage/LowStorageDialog.kt index a7da992334..95ee489d50 100644 --- a/modules/services/views/src/main/java/au/com/shiftyjelly/pocketcasts/views/lowstorage/LowStorageDialog.kt +++ b/modules/services/views/src/main/java/au/com/shiftyjelly/pocketcasts/views/lowstorage/LowStorageDialog.kt @@ -1,5 +1,6 @@ package au.com.shiftyjelly.pocketcasts.views.lowstorage +import android.view.ViewGroup import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -10,7 +11,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -22,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground +import au.com.shiftyjelly.pocketcasts.compose.bottomsheet.ModalBottomSheet import au.com.shiftyjelly.pocketcasts.compose.buttons.RowButton import au.com.shiftyjelly.pocketcasts.compose.buttons.RowOutlinedButton import au.com.shiftyjelly.pocketcasts.compose.components.TextH20 @@ -30,9 +35,48 @@ import au.com.shiftyjelly.pocketcasts.compose.preview.ThemePreviewParameterProvi import au.com.shiftyjelly.pocketcasts.compose.theme import au.com.shiftyjelly.pocketcasts.ui.theme.Theme import au.com.shiftyjelly.pocketcasts.utils.Util +import kotlinx.coroutines.launch import au.com.shiftyjelly.pocketcasts.images.R as IR import au.com.shiftyjelly.pocketcasts.localization.R as LR +@Composable +fun LowStorageLaunchBottomSheet( + parent: ViewGroup, + modifier: Modifier = Modifier, + shouldShow: Boolean = true, + totalDownloadSize: Long, + onManageDownloadsClick: () -> Unit, + onMaybeLaterClick: () -> Unit, + onExpanded: () -> Unit, +) { + val sheetState = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Hidden, + skipHalfExpanded = true, + ) + val coroutineScope = rememberCoroutineScope() + + ModalBottomSheet( + parent = parent, + shouldShow = shouldShow, + customSheetState = sheetState, + onExpanded = onExpanded, + customContent = { + LowStorageDialog( + modifier = modifier, + totalDownloadSize = totalDownloadSize, + onManageDownloadsClick = { + coroutineScope.launch { sheetState.hide() } + onManageDownloadsClick.invoke() + }, + onMaybeLaterClick = { + coroutineScope.launch { sheetState.hide() } + onMaybeLaterClick.invoke() + }, + ) + }, + ) +} + @Composable internal fun LowStorageDialog( modifier: Modifier = Modifier, @@ -120,3 +164,7 @@ fun PreviewLowStorageDialog(@PreviewParameter(ThemePreviewParameterProvider::cla ) } } + +interface LowStorageBottomSheetListener { + fun showModal() +}