Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Playback Effects: Hide segmented control for local files #3097

Merged
merged 16 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand Down Expand Up @@ -71,7 +72,7 @@ import au.com.shiftyjelly.pocketcasts.compose.bars.NavigationIconButton
import au.com.shiftyjelly.pocketcasts.compose.bars.SystemBarsStyles
import au.com.shiftyjelly.pocketcasts.compose.components.AutoResizeText
import au.com.shiftyjelly.pocketcasts.compose.components.HorizontalPagerWrapper
import au.com.shiftyjelly.pocketcasts.compose.components.StyledToggle
import au.com.shiftyjelly.pocketcasts.compose.components.SegmentedTabBar
import au.com.shiftyjelly.pocketcasts.compose.components.TextH30
import au.com.shiftyjelly.pocketcasts.compose.images.OfferBadge
import au.com.shiftyjelly.pocketcasts.compose.images.SubscriptionBadge
Expand Down Expand Up @@ -255,16 +256,18 @@ private fun UpgradeLayoutOriginal(
.padding(bottom = 24.dp),
contentAlignment = Alignment.Center,
) {
StyledToggle(
SegmentedTabBar(
items = state.subscriptionFrequencies
.map { stringResource(id = it.localisedLabelRes) },
defaultSelectedItemIndex = state.subscriptionFrequencies.indexOf(
selectedIndex = state.subscriptionFrequencies.indexOf(
state.currentSubscriptionFrequency,
),
) {
val selectedFrequency = state.subscriptionFrequencies[it]
onSubscriptionFrequencyChanged(selectedFrequency)
}
onItemSelected = {
val selectedFrequency = state.subscriptionFrequencies[it]
onSubscriptionFrequencyChanged(selectedFrequency)
},
modifier = Modifier.width(IntrinsicSize.Max),
)
}

FeatureCards(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,34 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.asFlow
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
import au.com.shiftyjelly.pocketcasts.analytics.SourceView
import au.com.shiftyjelly.pocketcasts.compose.components.SegmentedTabBar
import au.com.shiftyjelly.pocketcasts.compose.components.SegmentedTabBarDefaults
import au.com.shiftyjelly.pocketcasts.compose.theme
import au.com.shiftyjelly.pocketcasts.localization.helper.TimeHelper
import au.com.shiftyjelly.pocketcasts.models.entity.Podcast
import au.com.shiftyjelly.pocketcasts.models.to.PlaybackEffects
import au.com.shiftyjelly.pocketcasts.models.type.TrimMode
import au.com.shiftyjelly.pocketcasts.player.R
import au.com.shiftyjelly.pocketcasts.player.databinding.FragmentEffectsBinding
import au.com.shiftyjelly.pocketcasts.player.viewmodel.PlayerViewModel
import au.com.shiftyjelly.pocketcasts.player.viewmodel.PlayerViewModel.PlaybackEffectsSettingsTab
import au.com.shiftyjelly.pocketcasts.preferences.Settings
import au.com.shiftyjelly.pocketcasts.repositories.images.PocketCastsImageRequestFactory
import au.com.shiftyjelly.pocketcasts.repositories.images.loadInto
Expand All @@ -29,12 +45,15 @@ import au.com.shiftyjelly.pocketcasts.ui.helper.StatusBarColor
import au.com.shiftyjelly.pocketcasts.ui.theme.ThemeColor
import au.com.shiftyjelly.pocketcasts.utils.extensions.dpToPx
import au.com.shiftyjelly.pocketcasts.utils.extensions.roundedSpeed
import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature
import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag
import au.com.shiftyjelly.pocketcasts.views.extensions.applyColor
import au.com.shiftyjelly.pocketcasts.views.extensions.updateTint
import au.com.shiftyjelly.pocketcasts.views.fragments.BaseDialogFragment
import com.google.android.material.button.MaterialButtonToggleGroup
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.flow.distinctUntilChangedBy
import au.com.shiftyjelly.pocketcasts.localization.R as LR

@AndroidEntryPoint
Expand Down Expand Up @@ -63,6 +82,10 @@ class EffectsFragment : BaseDialogFragment(), CompoundButton.OnCheckedChangeList
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentEffectsBinding.inflate(inflater, container, false)

if (FeatureFlag.isEnabled(Feature.CUSTOM_PLAYBACK_SETTINGS)) {
binding?.setupEffectsSettingsSegmentedTabBar()
}

viewModel.effectsLive.value?.let { update(it) } // Make sure the window is the correct size before opening or else it won't expand properly
viewModel.effectsLive.observe(viewLifecycleOwner) { podcastEffectsPair ->
update(podcastEffectsPair)
Expand All @@ -84,13 +107,14 @@ class EffectsFragment : BaseDialogFragment(), CompoundButton.OnCheckedChangeList
binding = null
}

private fun update(podcastEffectsPair: PlayerViewModel.PodcastEffectsPair) {
val podcast = podcastEffectsPair.podcast
val effects = podcastEffectsPair.effects
private fun update(podcastEffectsData: PlayerViewModel.PodcastEffectsData) {
val podcast = podcastEffectsData.podcast
val effects = podcastEffectsData.effects

val binding = binding ?: return

binding.globalEffectsCard.isVisible = podcast.overrideGlobalEffects
binding.globalEffectsCard.isVisible = !FeatureFlag.isEnabled(Feature.CUSTOM_PLAYBACK_SETTINGS) &&
podcast.overrideGlobalEffects

imageRequestFactory.create(podcast).loadInto(binding.podcastEffectsImage)

Expand Down Expand Up @@ -246,4 +270,59 @@ class EffectsFragment : BaseDialogFragment(), CompoundButton.OnCheckedChangeList
private fun trackPlaybackEffectsEvent(event: AnalyticsEvent, props: Map<String, Any> = emptyMap()) {
playbackManager.trackPlaybackEffectsEvent(event, props, SourceView.PLAYER_PLAYBACK_EFFECTS)
}

private fun FragmentEffectsBinding.setupEffectsSettingsSegmentedTabBar() {
effectsSettingsSegmentedTabBar.setContent {
val podcastEffectsData by viewModel.effectsLive.asFlow()
.distinctUntilChangedBy { it.podcast }
MiSikora marked this conversation as resolved.
Show resolved Hide resolved
.collectAsStateWithLifecycle(null)
val podcast = podcastEffectsData?.podcast ?: return@setContent

if (podcastEffectsData?.showCustomEffectsSettings == true) {
EffectsSettingsSegmentedTabBar(
selectedItem = if (podcastEffectsData?.podcast?.overrideGlobalEffects == true) {
PlaybackEffectsSettingsTab.ThisPodcast
} else {
PlaybackEffectsSettingsTab.AllPodcasts
},
onItemSelected = {
viewModel.onEffectsSettingsSegmentedTabSelected(podcast, PlaybackEffectsSettingsTab.entries[it])
},
modifier = Modifier
.padding(top = 24.dp),
)
}
}
}

@Composable
private fun EffectsSettingsSegmentedTabBar(
modifier: Modifier = Modifier,
selectedItem: PlaybackEffectsSettingsTab,
onItemSelected: (selectedItemIndex: Int) -> Unit,
) {
SegmentedTabBar(
items = PlaybackEffectsSettingsTab.entries.map { stringResource(it.labelResId) },
selectedIndex = PlaybackEffectsSettingsTab.entries.indexOf(selectedItem),
colors = SegmentedTabBarDefaults.colors.copy(
selectedTabBackgroundColor = MaterialTheme.theme.colors.playerContrast06.copy(alpha = .1f),
borderColor = MaterialTheme.theme.colors.playerContrast03.copy(alpha = .4f),
),
cornerRadius = 120.dp,
textStyle = SegmentedTabBarDefaults.textStyle.copy(
fontSize = 13.sp,
),
modifier = modifier.fillMaxWidth(),
onItemSelected = onItemSelected,
)
}

@Preview(widthDp = 360)
@Composable
private fun EffectsSettingsSegmentedBarPreview() {
EffectsSettingsSegmentedTabBar(
selectedItem = PlaybackEffectsSettingsTab.AllPodcasts,
onItemSelected = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package au.com.shiftyjelly.pocketcasts.player.viewmodel

import android.content.Context
import android.content.res.Resources
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
Expand All @@ -27,6 +28,7 @@ import au.com.shiftyjelly.pocketcasts.preferences.model.ArtworkConfiguration
import au.com.shiftyjelly.pocketcasts.preferences.model.ShelfItem
import au.com.shiftyjelly.pocketcasts.repositories.bookmark.BookmarkManager
import au.com.shiftyjelly.pocketcasts.repositories.di.ApplicationScope
import au.com.shiftyjelly.pocketcasts.repositories.di.IoDispatcher
import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager
import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackState
import au.com.shiftyjelly.pocketcasts.repositories.playback.SleepTimer
Expand Down Expand Up @@ -60,6 +62,7 @@ import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -86,12 +89,13 @@ class PlayerViewModel @Inject constructor(
private val episodeAnalytics: EpisodeAnalytics,
@ApplicationContext private val context: Context,
@ApplicationScope private val applicationScope: CoroutineScope,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel(), CoroutineScope {

override val coroutineContext: CoroutineContext
get() = Dispatchers.Default

data class PodcastEffectsPair(val podcast: Podcast, val effects: PlaybackEffects)
data class PodcastEffectsData(val podcast: Podcast, val effects: PlaybackEffects, val showCustomEffectsSettings: Boolean = true)
data class PlayerHeader(
val positionMs: Int = 0,
val durationMs: Int = -1,
Expand Down Expand Up @@ -252,7 +256,7 @@ class PlayerViewModel @Inject constructor(

val upNextLive: LiveData<List<Any>> = upNextPlusData.toFlowable(BackpressureStrategy.LATEST).toLiveData()

val effectsObservable: Flowable<PodcastEffectsPair> = playbackStateObservable
val effectsObservable: Flowable<PodcastEffectsData> = playbackStateObservable
.toFlowable(BackpressureStrategy.LATEST)
.map { it.episodeUuid }
.switchMap { episodeManager.observeEpisodeByUuidRx(it) }
Expand All @@ -263,7 +267,14 @@ class PlayerViewModel @Inject constructor(
Flowable.just(Podcast.userPodcast.copy(overrideGlobalEffects = false))
}
}
.map { PodcastEffectsPair(it, if (it.overrideGlobalEffects) it.playbackEffects else settings.globalPlaybackEffects.value) }
.map { podcast ->
val isUserPodcast = podcast.uuid == Podcast.userPodcast.uuid
PodcastEffectsData(
podcast = podcast,
effects = if (podcast.overrideGlobalEffects) podcast.playbackEffects else settings.globalPlaybackEffects.value,
showCustomEffectsSettings = !isUserPodcast,
)
}
.doOnNext { Timber.i("Effects: Podcast: ${it.podcast.overrideGlobalEffects} ${it.effects}") }
.observeOn(AndroidSchedulers.mainThread())
val effectsLive = effectsObservable.toLiveData()
Expand Down Expand Up @@ -654,6 +665,20 @@ class PlayerViewModel @Inject constructor(
}
}

fun onEffectsSettingsSegmentedTabSelected(podcast: Podcast, selectedTab: PlaybackEffectsSettingsTab) {
val currentEpisode = playbackManager.getCurrentEpisode()
val isCurrentPodcast = currentEpisode?.podcastOrSubstituteUuid == podcast.uuid
if (!isCurrentPodcast) return
viewModelScope.launch(ioDispatcher) {
val override = selectedTab == PlaybackEffectsSettingsTab.ThisPodcast
podcastManager.updateOverrideGlobalEffects(podcast, override)

val effects = if (override) podcast.playbackEffects else settings.globalPlaybackEffects.value
podcast.overrideGlobalEffects = override
saveEffects(effects, podcast)
}
}

fun clearPodcastEffects(podcast: Podcast) {
launch {
podcastManager.updateOverrideGlobalEffects(podcast, false)
Expand Down Expand Up @@ -711,4 +736,9 @@ class PlayerViewModel @Inject constructor(
private const val podcastUuid = "podcast_uuid"
fun transcriptDismissed(episodeId: String, podcastId: String) = mapOf(episodeId to episodeUuid, podcastId to podcastUuid)
}

enum class PlaybackEffectsSettingsTab(@StringRes val labelResId: Int) {
AllPodcasts(LR.string.podcasts_all),
ThisPodcast(LR.string.podcast_this),
}
}
16 changes: 13 additions & 3 deletions modules/features/player/src/main/res/layout/fragment_effects.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,23 @@
style="@style/H30"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:text="@string/podcast_playback_effects"
android:textColor="?attr/player_contrast_01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<androidx.compose.ui.platform.ComposeView
android:id="@+id/effectsSettingsSegmentedTabBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/effectsTitle" />

<ImageView
android:id="@+id/iconSpeed"
android:layout_width="29dp"
Expand All @@ -47,8 +57,8 @@
android:padding="2dp"
android:src="@drawable/ic_speed"
app:tint="?attr/player_contrast_02"
app:layout_constraintLeft_toLeftOf="@id/effectsTitle"
app:layout_constraintTop_toBottomOf="@id/effectsTitle" />
app:layout_constraintLeft_toLeftOf="@id/effectsSettingsSegmentedTabBar"
app:layout_constraintTop_toBottomOf="@id/effectsSettingsSegmentedTabBar" />

<ImageButton
android:id="@+id/btnSpeedUp"
Expand Down
Loading