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 Settings: Add Feature Flag and add segmented tab bar #3065

Merged
merged 6 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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(
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,11 +7,23 @@ 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.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 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
Expand All @@ -29,6 +41,8 @@ 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
Expand Down Expand Up @@ -63,6 +77,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 Down Expand Up @@ -246,4 +264,39 @@ 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 {
EffectsSettingsSegmentedTabBar(
modifier = Modifier
.padding(top = 24.dp),
)
}
}

@Composable
private fun EffectsSettingsSegmentedTabBar(
modifier: Modifier = Modifier,
) {
SegmentedTabBar(
items = listOf(stringResource(LR.string.podcasts_all), stringResource(LR.string.podcast_this)),
defaultSelectedItemIndex = 0,
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 = {},
)
}

@Preview(widthDp = 360)
@Composable
private fun EffectsSettingsSegmentedBarPreview() {
EffectsSettingsSegmentedTabBar()
}
}
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package au.com.shiftyjelly.pocketcasts.compose.components

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
import com.airbnb.android.showkase.annotation.ShowkaseComposable
import au.com.shiftyjelly.pocketcasts.localization.R as LR

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SegmentedTabBar(
items: List<String>,
modifier: Modifier = Modifier,
defaultSelectedItemIndex: Int = 0,
colors: SegmentedTabBarColors = SegmentedTabBarDefaults.colors,
cornerRadius: Dp = SegmentedTabBarDefaults.cornerRadius,
border: BorderStroke = BorderStroke(SegmentedTabBarDefaults.borderThickness, colors.borderColor),
textStyle: TextStyle = SegmentedTabBarDefaults.textStyle,
onItemSelected: (selectedItemIndex: Int) -> Unit,
) {
var selectedIndex by remember { mutableIntStateOf(defaultSelectedItemIndex) }
Surface(
shape = RoundedCornerShape(cornerRadius),
border = border,
color = Color.Transparent,
modifier = modifier
.height(SegmentedTabBarDefaults.height),
) {
Row(
horizontalArrangement = Arrangement.Absolute.SpaceBetween,
) {
items.forEachIndexed { index, text ->
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
SegmentedTab(
isSelected = selectedIndex == index,
text = text,
textStyle = textStyle,
colors = colors,
onItemSelected = {
selectedIndex = index
onItemSelected(index)
},
)
if (index < items.size - 1) {
Divider(colors.borderColor)
}
}
}
}
}
}

@Composable
private fun RowScope.SegmentedTab(
isSelected: Boolean,
text: String,
textStyle: TextStyle,
colors: SegmentedTabBarColors,
onItemSelected: () -> Unit,
) {
OutlinedButton(
shape = RectangleShape,
border = null,
colors = ButtonDefaults.buttonColors(
backgroundColor = if (isSelected) colors.selectedTabBackgroundColor else colors.unSelectedTabBackgroundColor,
),
onClick = onItemSelected,
modifier = Modifier.Companion
.weight(1f)
.fillMaxSize()
.defaultMinSize(minWidth = SegmentedTabBarDefaults.tabMinSize)
.semantics { role = Role.Tab },
) {
if (isSelected) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
tint = colors.selectedTabIconColor,
)
Spacer(modifier = Modifier.width(8.dp))
}
Text(
text = text,
color = if (isSelected) colors.selectedTabTextColor else colors.unSelectedTabTextColor,
fontSize = textStyle.fontSize,
letterSpacing = textStyle.letterSpacing,
lineHeight = 18.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.W500,
)
}
}

@Composable
private fun Divider(
color: Color,
) {
Spacer(
modifier = Modifier
.background(color)
.width(SegmentedTabBarDefaults.borderThickness)
.height(SegmentedTabBarDefaults.height),
)
}

object SegmentedTabBarDefaults {
val colors = SegmentedTabBarColors()
val cornerRadius: Dp = 24.dp
val borderThickness = 1.0.dp
val textStyle = TextStyle(
fontSize = 15.sp,
letterSpacing = -(0.08).sp,
)
val height: Dp = 40.dp
val tabMinSize: Dp = 150.dp
}

data class SegmentedTabBarColors(
val selectedTabBackgroundColor: Color = Color.White.copy(alpha = .1f),
val selectedTabTextColor: Color = Color.White,
val selectedTabIconColor: Color = Color.White,
val unSelectedTabBackgroundColor: Color = Color.Transparent,
val unSelectedTabTextColor: Color = Color.White,
val unSelectedTabIconColor: Color = Color.White,
val borderColor: Color = Color.White.copy(alpha = .4f),
)

@ShowkaseComposable(name = "SegmentedTabBar", group = "TabBar")
@Preview
@Composable
fun SegmentedTabBarPreview() {
AppThemeWithBackground(Theme.ThemeType.DARK) {
SegmentedTabBar(
items = listOf(stringResource(LR.string.plus_yearly), stringResource(LR.string.plus_monthly)),
onItemSelected = {},
modifier = Modifier.width(IntrinsicSize.Max),
)
}
}
Loading
Loading