mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor(download): share download UI handling
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package hu.bbara.purefin.ui.common.permission
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
@Composable
|
||||
fun rememberNotificationPermissionGate(): (action: () -> Unit) -> Unit {
|
||||
val pendingAction = remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) {
|
||||
// Proceed regardless — notification permission is nice-to-have for downloads.
|
||||
val action = pendingAction.value
|
||||
pendingAction.value = null
|
||||
action?.invoke()
|
||||
}
|
||||
|
||||
return remember(launcher) {
|
||||
{ action ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
pendingAction.value = action
|
||||
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
package hu.bbara.purefin.ui.screen.episode
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import hu.bbara.purefin.core.download.DownloadState
|
||||
import hu.bbara.purefin.core.feature.content.episode.EpisodeScreenViewModel
|
||||
import hu.bbara.purefin.core.image.ArtworkKind
|
||||
@@ -26,6 +22,7 @@ import hu.bbara.purefin.model.Episode
|
||||
import hu.bbara.purefin.navigation.LocalNavigationBackStack
|
||||
import hu.bbara.purefin.ui.common.media.MediaDetailScaffold
|
||||
import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow
|
||||
import hu.bbara.purefin.ui.common.permission.rememberNotificationPermissionGate
|
||||
import hu.bbara.purefin.ui.screen.episode.components.EpisodeDetails
|
||||
import hu.bbara.purefin.ui.screen.episode.components.EpisodeTopBar
|
||||
import hu.bbara.purefin.ui.screen.episode.components.EpisodeTopBarShortcut
|
||||
@@ -48,19 +45,13 @@ fun EpisodeScreen(
|
||||
|
||||
val episode = viewModel.episode.collectAsStateWithLifecycle()
|
||||
val downloadState = viewModel.downloadState.collectAsStateWithLifecycle()
|
||||
|
||||
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { _ ->
|
||||
// Proceed with download regardless — notification is nice-to-have
|
||||
viewModel.onDownloadClick()
|
||||
}
|
||||
val requestNotificationPermission = rememberNotificationPermissionGate()
|
||||
|
||||
val onDownloadClick = {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
&& downloadState.value is DownloadState.NotDownloaded
|
||||
) {
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
if (downloadState.value is DownloadState.NotDownloaded) {
|
||||
requestNotificationPermission {
|
||||
viewModel.onDownloadClick()
|
||||
}
|
||||
} else {
|
||||
viewModel.onDownloadClick()
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Cast
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.DownloadDone
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -32,8 +29,8 @@ import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.core.download.DownloadState
|
||||
import hu.bbara.purefin.model.Episode
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
import hu.bbara.purefin.ui.common.button.DownloadActionButton
|
||||
import hu.bbara.purefin.ui.common.button.GhostIconButton
|
||||
import hu.bbara.purefin.ui.common.button.MediaActionButton
|
||||
import hu.bbara.purefin.ui.common.button.MediaResumeButton
|
||||
import hu.bbara.purefin.ui.common.media.MediaPlaybackSettings
|
||||
import hu.bbara.purefin.ui.common.media.MediaSynopsis
|
||||
@@ -137,16 +134,8 @@ internal fun EpisodeDetails(
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
Row() {
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
iconColor = MaterialTheme.colorScheme.onSurface,
|
||||
icon = when (downloadState) {
|
||||
is DownloadState.NotDownloaded -> Icons.Outlined.Download
|
||||
is DownloadState.Downloading -> Icons.Outlined.Close
|
||||
is DownloadState.Downloaded -> Icons.Outlined.DownloadDone
|
||||
is DownloadState.Failed -> Icons.Outlined.Download
|
||||
},
|
||||
height = 48.dp,
|
||||
DownloadActionButton(
|
||||
downloadState = downloadState,
|
||||
modifier = Modifier.testTag(EpisodeDownloadButtonTag),
|
||||
onClick = onDownloadClick
|
||||
)
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package hu.bbara.purefin.ui.screen.movie
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import hu.bbara.purefin.core.download.DownloadState
|
||||
import hu.bbara.purefin.core.feature.content.movie.MovieScreenViewModel
|
||||
import hu.bbara.purefin.core.image.ArtworkKind
|
||||
@@ -23,6 +19,7 @@ import hu.bbara.purefin.model.CastMember
|
||||
import hu.bbara.purefin.model.Movie
|
||||
import hu.bbara.purefin.ui.common.media.MediaDetailScaffold
|
||||
import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow
|
||||
import hu.bbara.purefin.ui.common.permission.rememberNotificationPermissionGate
|
||||
import hu.bbara.purefin.ui.screen.movie.components.MovieDetails
|
||||
import hu.bbara.purefin.ui.screen.movie.components.MovieTopBar
|
||||
import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen
|
||||
@@ -41,19 +38,13 @@ fun MovieScreen(
|
||||
|
||||
val movieItem = viewModel.movie.collectAsStateWithLifecycle()
|
||||
val downloadState = viewModel.downloadState.collectAsStateWithLifecycle()
|
||||
|
||||
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { granted ->
|
||||
// Proceed with download regardless — notification is nice-to-have
|
||||
viewModel.onDownloadClick()
|
||||
}
|
||||
val requestNotificationPermission = rememberNotificationPermissionGate()
|
||||
|
||||
val onDownloadClick = {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
&& downloadState.value is DownloadState.NotDownloaded
|
||||
) {
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
if (downloadState.value is DownloadState.NotDownloaded) {
|
||||
requestNotificationPermission {
|
||||
viewModel.onDownloadClick()
|
||||
}
|
||||
} else {
|
||||
viewModel.onDownloadClick()
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Cast
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.DownloadDone
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -28,8 +25,8 @@ import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.core.download.DownloadState
|
||||
import hu.bbara.purefin.model.Movie
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
import hu.bbara.purefin.ui.common.button.DownloadActionButton
|
||||
import hu.bbara.purefin.ui.common.button.GhostIconButton
|
||||
import hu.bbara.purefin.ui.common.button.MediaActionButton
|
||||
import hu.bbara.purefin.ui.common.button.MediaResumeButton
|
||||
import hu.bbara.purefin.ui.common.media.MediaPlaybackSettings
|
||||
import hu.bbara.purefin.ui.common.media.MediaSynopsis
|
||||
@@ -98,16 +95,8 @@ internal fun MovieDetails(
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
Row() {
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
iconColor = MaterialTheme.colorScheme.onSurface,
|
||||
icon = when (downloadState) {
|
||||
is DownloadState.NotDownloaded -> Icons.Outlined.Download
|
||||
is DownloadState.Downloading -> Icons.Outlined.Close
|
||||
is DownloadState.Downloaded -> Icons.Outlined.DownloadDone
|
||||
is DownloadState.Failed -> Icons.Outlined.Download
|
||||
},
|
||||
height = 48.dp,
|
||||
DownloadActionButton(
|
||||
downloadState = downloadState,
|
||||
modifier = Modifier.testTag(MovieDownloadButtonTag),
|
||||
onClick = onDownloadClick
|
||||
)
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package hu.bbara.purefin.ui.screen.series
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -32,6 +28,7 @@ import hu.bbara.purefin.model.Season
|
||||
import hu.bbara.purefin.model.Series
|
||||
import hu.bbara.purefin.ui.common.media.MediaDetailScaffold
|
||||
import hu.bbara.purefin.ui.common.media.MediaSynopsis
|
||||
import hu.bbara.purefin.ui.common.permission.rememberNotificationPermissionGate
|
||||
import hu.bbara.purefin.ui.screen.series.components.CastRow
|
||||
import hu.bbara.purefin.ui.screen.series.components.EpisodeCarousel
|
||||
import hu.bbara.purefin.ui.screen.series.components.SeasonTabs
|
||||
@@ -54,37 +51,7 @@ fun SeriesScreen(
|
||||
}
|
||||
|
||||
val seriesState = viewModel.series.collectAsStateWithLifecycle()
|
||||
var pendingDownloadOption by remember { mutableStateOf<Pair<SeriesDownloadOption, Season>?>(null) }
|
||||
|
||||
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { _ ->
|
||||
// Proceed with download regardless — notification is nice-to-have
|
||||
val pendingOption = pendingDownloadOption
|
||||
pendingDownloadOption = null
|
||||
val (option, selectedSeason) = pendingOption ?: return@rememberLauncherForActivityResult
|
||||
val seriesData = seriesState.value ?: return@rememberLauncherForActivityResult
|
||||
val canPerformOption = when (option) {
|
||||
SeriesDownloadOption.SEASON -> viewModel.seasonDownloadState.value is DownloadState.NotDownloaded
|
||||
SeriesDownloadOption.SERIES -> viewModel.seriesDownloadState.value is DownloadState.NotDownloaded
|
||||
SeriesDownloadOption.SMART -> !viewModel.isSmartDownloadEnabled.value
|
||||
SeriesDownloadOption.DELETE_SMART -> true
|
||||
}
|
||||
if (!canPerformOption) return@rememberLauncherForActivityResult
|
||||
when (option) {
|
||||
SeriesDownloadOption.SEASON ->
|
||||
viewModel.downloadSeason(seriesData.id, selectedSeason.id)
|
||||
|
||||
SeriesDownloadOption.SERIES ->
|
||||
viewModel.downloadSeries(seriesData)
|
||||
|
||||
SeriesDownloadOption.SMART ->
|
||||
viewModel.enableSmartDownload(seriesData.id)
|
||||
|
||||
SeriesDownloadOption.DELETE_SMART ->
|
||||
viewModel.deleteSmartDownloads(seriesData.id)
|
||||
}
|
||||
}
|
||||
val requestNotificationPermission = rememberNotificationPermissionGate()
|
||||
|
||||
val seriesData = seriesState.value
|
||||
if (seriesData != null) {
|
||||
@@ -95,15 +62,14 @@ fun SeriesScreen(
|
||||
val seasonDownloadState = viewModel.seasonDownloadState.collectAsStateWithLifecycle().value
|
||||
val isSmartDownloadEnabled = viewModel.isSmartDownloadEnabled.collectAsStateWithLifecycle().value
|
||||
|
||||
fun canPerformDownloadOption(option: SeriesDownloadOption): Boolean = when (option) {
|
||||
SeriesDownloadOption.SEASON -> seasonDownloadState is DownloadState.NotDownloaded
|
||||
SeriesDownloadOption.SERIES -> seriesDownloadState is DownloadState.NotDownloaded
|
||||
SeriesDownloadOption.SMART -> !isSmartDownloadEnabled
|
||||
SeriesDownloadOption.DELETE_SMART -> true
|
||||
}
|
||||
fun canPerformDownloadOption(option: SeriesDownloadOption): Boolean =
|
||||
option.canPerform(
|
||||
seriesDownloadState = seriesDownloadState,
|
||||
seasonDownloadState = seasonDownloadState,
|
||||
isSmartDownloadEnabled = isSmartDownloadEnabled
|
||||
)
|
||||
|
||||
fun performDownloadOption(option: SeriesDownloadOption, selectedSeason: Season) {
|
||||
if (!canPerformDownloadOption(option)) return
|
||||
when (option) {
|
||||
SeriesDownloadOption.SEASON ->
|
||||
viewModel.downloadSeason(seriesData.id, selectedSeason.id)
|
||||
@@ -119,16 +85,6 @@ fun SeriesScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldRequestNotificationPermission(option: SeriesDownloadOption): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return false
|
||||
return when (option) {
|
||||
SeriesDownloadOption.SEASON,
|
||||
SeriesDownloadOption.SERIES,
|
||||
SeriesDownloadOption.SMART -> canPerformDownloadOption(option)
|
||||
SeriesDownloadOption.DELETE_SMART -> false
|
||||
}
|
||||
}
|
||||
|
||||
SeriesScreenInternal(
|
||||
series = seriesData,
|
||||
seriesDownloadState = seriesDownloadState,
|
||||
@@ -136,9 +92,10 @@ fun SeriesScreen(
|
||||
isSmartDownloadEnabled = isSmartDownloadEnabled,
|
||||
onDownloadOptionSelected = { option, selectedSeason ->
|
||||
if (canPerformDownloadOption(option)) {
|
||||
if (shouldRequestNotificationPermission(option)) {
|
||||
pendingDownloadOption = option to selectedSeason
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
if (option.requiresNotificationPermission()) {
|
||||
requestNotificationPermission {
|
||||
performDownloadOption(option, selectedSeason)
|
||||
}
|
||||
} else {
|
||||
performDownloadOption(option, selectedSeason)
|
||||
}
|
||||
@@ -264,6 +221,24 @@ private fun SeriesHeroContent(
|
||||
SeriesMetaChips(series = series)
|
||||
}
|
||||
|
||||
private fun SeriesDownloadOption.canPerform(
|
||||
seriesDownloadState: DownloadState,
|
||||
seasonDownloadState: DownloadState,
|
||||
isSmartDownloadEnabled: Boolean,
|
||||
): Boolean = when (this) {
|
||||
SeriesDownloadOption.SEASON -> seasonDownloadState is DownloadState.NotDownloaded
|
||||
SeriesDownloadOption.SERIES -> seriesDownloadState is DownloadState.NotDownloaded
|
||||
SeriesDownloadOption.SMART -> !isSmartDownloadEnabled
|
||||
SeriesDownloadOption.DELETE_SMART -> true
|
||||
}
|
||||
|
||||
private fun SeriesDownloadOption.requiresNotificationPermission(): Boolean = when (this) {
|
||||
SeriesDownloadOption.SEASON,
|
||||
SeriesDownloadOption.SERIES,
|
||||
SeriesDownloadOption.SMART -> true
|
||||
SeriesDownloadOption.DELETE_SMART -> false
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SeriesScreenPreview() {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package hu.bbara.purefin.ui.common.button
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.DownloadDone
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.download.DownloadState
|
||||
|
||||
@Composable
|
||||
fun DownloadActionButton(
|
||||
downloadState: DownloadState,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
height: Dp = 48.dp,
|
||||
) {
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
iconColor = MaterialTheme.colorScheme.onSurface,
|
||||
icon = when (downloadState) {
|
||||
is DownloadState.NotDownloaded -> Icons.Outlined.Download
|
||||
is DownloadState.Downloading -> Icons.Outlined.Close
|
||||
is DownloadState.Downloaded -> Icons.Outlined.DownloadDone
|
||||
is DownloadState.Failed -> Icons.Outlined.Download
|
||||
},
|
||||
height = height,
|
||||
modifier = modifier,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user