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 4 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,29 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.compose.foundation.layout.PaddingValues
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.graphics.Color
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.graphics.toColorInt
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
Expand All @@ -25,10 +43,13 @@ import au.com.shiftyjelly.pocketcasts.repositories.images.loadInto
import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager
import au.com.shiftyjelly.pocketcasts.repositories.user.StatsManager
import au.com.shiftyjelly.pocketcasts.ui.extensions.themed
import au.com.shiftyjelly.pocketcasts.ui.helper.ColorUtils
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 @@ -94,6 +115,10 @@ class EffectsFragment : BaseDialogFragment(), CompoundButton.OnCheckedChangeList

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

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

binding.lblSpeed.text = String.format("%.1fx", effects.playbackSpeed)

binding.btnSpeedUp.setOnClickListener(this)
Expand Down Expand Up @@ -246,4 +271,48 @@ 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 playingEpisodeState by viewModel.playingEpisodeLive.asFlow().collectAsStateWithLifecycle(null)
playingEpisodeState?.let {
val (_, podcastHeaderBackgroundColor) = it
EffectsSettingsSegmentedTabBar(
modifier = Modifier
.padding(top = 24.dp),
selectedTabTextColor = Color(ColorUtils.colorIntToHexString(podcastHeaderBackgroundColor).toColorInt()),
)
}
}
}

@Composable
private fun EffectsSettingsSegmentedTabBar(
modifier: Modifier = Modifier,
selectedTabTextColor: Color = Color.Black,
) {
SegmentedTabBar(
items = listOf(stringResource(LR.string.podcasts_all), stringResource(LR.string.podcast_this)),
defaultSelectedItemIndex = 0,
colors = SegmentedTabBarDefaults.colors.copy(
baseBackgroundColor = MaterialTheme.theme.colors.playerContrast06.copy(alpha = .1f),
selectedTabTextColor = selectedTabTextColor,
unSelectedTabTextColor = MaterialTheme.theme.colors.playerContrast02.copy(alpha = .5f),
),
cornerRadius = 7.dp,
outerPadding = 2.dp,
tabContentPadding = PaddingValues(horizontal = 10.dp, vertical = 3.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,148 @@
package au.com.shiftyjelly.pocketcasts.compose.components

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
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.draw.clip
import androidx.compose.ui.graphics.Color
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,
outerPadding: Dp = SegmentedTabBarDefaults.outerPadding,
tabContentPadding: PaddingValues = SegmentedTabBarDefaults.tabContentPadding,
textStyle: TextStyle = SegmentedTabBarDefaults.textStyle,
onItemSelected: (selectedItemIndex: Int) -> Unit,
) {
var selectedIndex by remember { mutableIntStateOf(defaultSelectedItemIndex) }

Box(
modifier = modifier
.clip(RoundedCornerShape(cornerRadius))
.background(colors.baseBackgroundColor)
.padding(outerPadding),
) {
Row(
horizontalArrangement = Arrangement.Absolute.SpaceBetween,
) {
items.forEachIndexed { index, item ->
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
OutlinedButton(
shape = RoundedCornerShape(cornerRadius),
border = null,
colors = ButtonDefaults.buttonColors(
backgroundColor = if (selectedIndex == index) colors.selectedTabBackgroundColor else colors.unSelectedTabBackgroundColor,
),
contentPadding = tabContentPadding,
onClick = {
selectedIndex = index
onItemSelected(selectedIndex)
},
modifier = Modifier
.weight(1f)
.defaultMinSize(minWidth = 1.dp, minHeight = 1.dp)
.semantics { role = Role.Tab },
) {
Text(
text = item,
color = if (selectedIndex == index) colors.selectedTabTextColor else colors.unSelectedTabTextColor,
fontSize = textStyle.fontSize,
letterSpacing = textStyle.letterSpacing,
lineHeight = 18.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.W500,
)
}
}
}
}
}
}

object SegmentedTabBarDefaults {
val colors = SegmentedTabBarColors()
val cornerRadius: Dp = 24.dp
val outerPadding: Dp = 4.dp
val tabContentPadding: PaddingValues = ButtonDefaults.ContentPadding
val textStyle = TextStyle(
fontSize = 15.sp,
letterSpacing = -(0.08).sp,
)
}

data class SegmentedTabBarColors(
val baseBackgroundColor: Color = Color.White.copy(alpha = .1f),
val selectedTabBackgroundColor: Color = Color.White,
val selectedTabTextColor: Color = Color.Black,
val unSelectedTabBackgroundColor: Color = Color.Transparent,
val unSelectedTabTextColor: Color = Color.White,
)

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

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