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

End of Year: Add stories structure #428

Merged
merged 9 commits into from
Oct 21, 2022
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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ dependencies {
implementation project(':modules:features:filters')
implementation project(':modules:features:navigation')
implementation project(':modules:features:account')
implementation project(':modules:features:endofyear')
}

task appStart(type: Exec, dependsOn: 'installDebug') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import au.com.shiftyjelly.pocketcasts.compose.bottomsheet.BottomSheetContentStat
import au.com.shiftyjelly.pocketcasts.compose.bottomsheet.ModalBottomSheet
import au.com.shiftyjelly.pocketcasts.databinding.ActivityMainBinding
import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment
import au.com.shiftyjelly.pocketcasts.endofyear.StoriesFragment
import au.com.shiftyjelly.pocketcasts.filters.FiltersFragment
import au.com.shiftyjelly.pocketcasts.localization.helper.LocaliseHelper
import au.com.shiftyjelly.pocketcasts.models.entity.Episode
Expand Down Expand Up @@ -465,7 +466,10 @@ class MainActivity :
summaryText = stringResource(LR.string.end_of_year_launch_modal_summary),
primaryButton = Button.Primary(
label = stringResource(LR.string.end_of_year_launch_modal_primary_button_title),
onClick = {}
onClick = {
StoriesFragment.newInstance()
.show(supportFragmentManager, "stories_dialog")
}
),
secondaryButton = Button.Secondary(
label = stringResource(LR.string.end_of_year_launch_modal_secondary_button_title),
Expand Down
18 changes: 18 additions & 0 deletions modules/features/endofyear/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apply from: "../../modules.gradle"

android {
namespace 'au.com.shiftyjelly.pocketcasts.endofyear'
buildFeatures {
viewBinding true
dataBinding = true
compose true
}
}

dependencies {
// services
implementation project(':modules:services:compose')
implementation project(':modules:services:localization')
implementation project(':modules:services:ui')
implementation project(':modules:services:views')
}
4 changes: 4 additions & 0 deletions modules/features/endofyear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package au.com.shiftyjelly.pocketcasts.endofyear

import androidx.annotation.FloatRange
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.dp

private val StrokeWidth = 2.dp
private val GapWidth = 8.dp
private val SegmentHeight = StrokeWidth
private const val IndicatorBackgroundOpacity = 0.24f

@Composable
fun SegmentedProgressIndicator(
@FloatRange(from = 0.0, to = 1.0) progress: Float,
modifier: Modifier = Modifier,
color: Color = Color.White,
backgroundColor: Color = color.copy(alpha = IndicatorBackgroundOpacity),
numberOfSegments: Int,
) {
Comment on lines +22 to +28
Copy link
Contributor Author

@ashiagr ashiagr Oct 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the logic is taken from Compose internal implementation of LinearProgressIndicator and this answer.

It doesn't support RTL animation right now.

Canvas(
modifier
.progressSemantics(progress)
.fillMaxWidth()
.height(SegmentHeight)
.focusable()
) {
drawSegmentsBackground(backgroundColor, numberOfSegments)
drawSegments(progress, color, numberOfSegments)
}
}

private fun DrawScope.drawSegmentsBackground(
color: Color,
numberOfSegments: Int,
) = drawSegments(1f, color, numberOfSegments)

private fun DrawScope.drawSegments(
endFraction: Float,
color: Color,
numberOfSegments: Int,
) {
val width = size.width
val height = size.height
// Start drawing from the vertical center of the stroke
val yOffset = height / 2

val barEnd = endFraction * width

val segmentWidth = calculateSegmentWidth(numberOfSegments)
val segmentAndGapWidth = segmentWidth + GapWidth.toPx()

repeat(numberOfSegments) { index ->
val xOffsetStart = index * segmentAndGapWidth
val shouldDrawLine = xOffsetStart < barEnd
if (shouldDrawLine) {
val xOffsetEnd = (xOffsetStart + segmentWidth).coerceAtMost(barEnd)
// Progress line
drawLine(color, Offset(xOffsetStart, yOffset), Offset(xOffsetEnd, yOffset), StrokeWidth.toPx())
}
}
}

private fun DrawScope.calculateSegmentWidth(
numberOfSegments: Int,
): Float {
val width = size.width
val gapsWidth = (numberOfSegments - 1) * GapWidth.toPx()
return (width - gapsWidth) / numberOfSegments
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package au.com.shiftyjelly.pocketcasts.endofyear

import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import au.com.shiftyjelly.pocketcasts.compose.AppTheme
import au.com.shiftyjelly.pocketcasts.ui.R
import au.com.shiftyjelly.pocketcasts.ui.helper.StatusBarColor
import au.com.shiftyjelly.pocketcasts.views.fragments.BaseDialogFragment

class StoriesFragment : BaseDialogFragment() {
override val statusBarColor: StatusBarColor
get() = StatusBarColor.Custom(Color.BLACK, true)

override fun onCreate(savedInstance: Bundle?) {
super.onCreate(savedInstance)
setStyle(STYLE_NORMAL, R.style.BottomSheetDialogThemeBlack)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
return ComposeView(requireContext()).apply {
setContent {
AppTheme(theme.activeTheme) {
ashiagr marked this conversation as resolved.
Show resolved Hide resolved
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
StoriesScreen(
onCloseClicked = { dismiss() },
)
}
}
}
}

companion object {
fun newInstance() = StoriesFragment()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package au.com.shiftyjelly.pocketcasts.endofyear

import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import au.com.shiftyjelly.pocketcasts.compose.AppTheme
import au.com.shiftyjelly.pocketcasts.compose.bars.NavigationButton
import au.com.shiftyjelly.pocketcasts.compose.buttons.RowOutlinedButton
import au.com.shiftyjelly.pocketcasts.compose.preview.ThemePreviewParameterProvider
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
import au.com.shiftyjelly.pocketcasts.localization.R as LR

private const val ProgressDurationMs = 5_000
private val ShareButtonStrokeWidth = 2.dp
private val StoryViewCornerSize = 10.dp
private const val NumberOfSegments = 2

@Composable
fun StoriesScreen(
onCloseClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
var running by remember { mutableStateOf(false) }
val progress: Float by animateFloatAsState(
ashiagr marked this conversation as resolved.
Show resolved Hide resolved
if (running) 1f else 0f,
animationSpec = tween(
durationMillis = ProgressDurationMs,
easing = LinearEasing
)
)
Box(modifier = modifier.background(color = Color.Black)) {
StoryView(color = Color.Gray)
SegmentedProgressIndicator(
progress = progress,
numberOfSegments = NumberOfSegments,
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
)
CloseButtonView(onCloseClicked)
}

LaunchedEffect(Unit) {
running = true
}
}

@Composable
private fun StoryView(
color: Color,
modifier: Modifier = Modifier,
) {
Column {
Box(
modifier = modifier
.fillMaxSize()
.weight(weight = 1f, fill = true)
.clip(RoundedCornerShape(StoryViewCornerSize))
.background(color = color)
) {}
ShareButton()
}
}

@Composable
private fun ShareButton() {
RowOutlinedButton(
text = stringResource(id = LR.string.share),
border = BorderStroke(ShareButtonStrokeWidth, Color.White),
colors = ButtonDefaults
.outlinedButtonColors(
backgroundColor = Color.Transparent,
contentColor = Color.White,
),
iconImage = Icons.Default.Share,
onClick = {}
)
}

@Composable
private fun CloseButtonView(
onCloseClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(top = 16.dp),
horizontalArrangement = Arrangement.End
) {
IconButton(
onClick = onCloseClicked
) {
Icon(
imageVector = NavigationButton.Close.image,
contentDescription = stringResource(NavigationButton.Close.contentDescription),
tint = Color.White
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun StoriesScreenPreview(
@PreviewParameter(ThemePreviewParameterProvider::class) themeType: Theme.ThemeType,
) {
AppTheme(themeType) {
StoriesScreen(
onCloseClicked = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ fun BottomSheetContent(

Spacer(modifier = modifier.height(16.dp))

Button(onClick = content.primaryButton.onClick) {
Button(
onClick = {
onDismiss.invoke()
content.primaryButton.onClick.invoke()
}
) {
Text(text = content.primaryButton.label)
}

Expand Down
Loading