Skip to content

Commit

Permalink
Merge pull request #505 from Automattic/task/410-story-snapshot
Browse files Browse the repository at this point in the history
End of Year: Story snapshot and share
  • Loading branch information
ashiagr authored Nov 1, 2022
2 parents fdcfddb + 22b0d71 commit bea60f1
Show file tree
Hide file tree
Showing 13 changed files with 339 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@ package au.com.shiftyjelly.pocketcasts.endofyear
import au.com.shiftyjelly.pocketcasts.endofyear.stories.Story
import kotlinx.coroutines.flow.Flow

abstract class StoriesDataSource {
protected abstract val stories: List<Story>

val numOfStories: Int
get() = stories.size
val totalLengthInMs
get() = storyLengthsInMs.sum() + gapLengthsInMs
val storyLengthsInMs: List<Long>
get() = stories.map { it.storyLength }
private val gapLengthsInMs: Long
get() = STORY_GAP_LENGTH_MS * numOfStories.minus(1).coerceAtLeast(0)

abstract suspend fun loadStories(): Flow<List<Story>>
abstract fun storyAt(index: Int): Story?

companion object {
const val STORY_GAP_LENGTH_MS = 100L
}
interface StoriesDataSource {
suspend fun loadStories(): Flow<List<Story>>
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
package au.com.shiftyjelly.pocketcasts.endofyear

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.viewModels
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.utils.FileUtil
import au.com.shiftyjelly.pocketcasts.views.fragments.BaseDialogFragment
import timber.log.Timber
import java.io.File
import au.com.shiftyjelly.pocketcasts.localization.R as LR

class StoriesFragment : BaseDialogFragment() {
private val viewModel: StoriesViewModel by viewModels()
override val statusBarColor: StatusBarColor
get() = StatusBarColor.Custom(Color.BLACK, true)

private val shareLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
/* Share activity dismissed, start paused story */
viewModel.start()
}

override fun onCreate(savedInstance: Bundle?) {
super.onCreate(savedInstance)
setStyle(STYLE_NORMAL, R.style.BottomSheetDialogThemeBlack)
Expand All @@ -35,12 +46,32 @@ class StoriesFragment : BaseDialogFragment() {
StoriesScreen(
viewModel = viewModel,
onCloseClicked = { dismiss() },
onShareClicked = { onCaptureBitmap ->
viewModel.onShareClicked(
onCaptureBitmap,
requireContext(),
::showShareForFile
)
}
)
}
}
}
}

private fun showShareForFile(file: File) {
val context = requireContext()
try {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "image/png"
val uri = FileUtil.createUriWithReadPermissions(file, intent, requireContext())
intent.putExtra(Intent.EXTRA_STREAM, uri)
shareLauncher.launch(Intent.createChooser(intent, context.getString(LR.string.end_of_year_share_via)))
} catch (e: Exception) {
Timber.e(e)
}
}

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

import android.graphics.Bitmap
import androidx.annotation.FloatRange
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
Expand Down Expand Up @@ -43,8 +44,9 @@ import au.com.shiftyjelly.pocketcasts.endofyear.StoriesViewModel.State
import au.com.shiftyjelly.pocketcasts.endofyear.stories.Story
import au.com.shiftyjelly.pocketcasts.endofyear.stories.StoryFake1
import au.com.shiftyjelly.pocketcasts.endofyear.stories.StoryFake2
import au.com.shiftyjelly.pocketcasts.endofyear.views.StoryFake1View
import au.com.shiftyjelly.pocketcasts.endofyear.views.StoryFake2View
import au.com.shiftyjelly.pocketcasts.endofyear.views.convertibleToBitmap
import au.com.shiftyjelly.pocketcasts.endofyear.views.stories.StoryFake1View
import au.com.shiftyjelly.pocketcasts.endofyear.views.stories.StoryFake2View
import au.com.shiftyjelly.pocketcasts.models.entity.Podcast
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
import au.com.shiftyjelly.pocketcasts.localization.R as LR
Expand All @@ -56,6 +58,7 @@ private val StoryViewCornerSize = 10.dp
fun StoriesScreen(
viewModel: StoriesViewModel,
onCloseClicked: () -> Unit,
onShareClicked: (() -> Bitmap) -> Unit,
) {
val state: State by viewModel.state.collectAsState()
val progress: Float by viewModel.progress.collectAsState()
Expand All @@ -67,8 +70,10 @@ fun StoriesScreen(
onSkipNext = { viewModel.skipNext() },
onPause = { viewModel.pause() },
onStart = { viewModel.start() },
onCloseClicked = onCloseClicked
onCloseClicked = onCloseClicked,
onShareClicked = onShareClicked,
)

State.Loading -> StoriesLoadingView(onCloseClicked)
State.Error -> StoriesErrorView(onCloseClicked)
}
Expand All @@ -83,81 +88,69 @@ private fun StoriesView(
onPause: () -> Unit,
onStart: () -> Unit,
onCloseClicked: () -> Unit,
onShareClicked: (() -> Bitmap) -> Unit,
modifier: Modifier = Modifier,
) {
var screenWidth by remember { mutableStateOf(1) }
var isPaused by remember { mutableStateOf(false) }
Box(
modifier = modifier
.fillMaxSize()
.background(color = Color.Black)
.onGloballyPositioned {
screenWidth = it.size.width
}
.pointerInput(Unit) {
detectTapGestures(
onTap = {
if (!isPaused) {
if (it.x > screenWidth / 2) {
onSkipNext()
} else {
onSkipPrevious()
}
}
},
onLongPress = {
isPaused = true
onPause()
},
onPress = {
awaitRelease()
if (isPaused) {
onStart()
isPaused = false
}
Column(modifier = modifier.background(color = Color.Black)) {
var onCaptureBitmap: (() -> Bitmap)? = null
state.currentStory?.let { story ->
Box(modifier = modifier.weight(weight = 1f, fill = true)) {
if (!story.isInteractive) {
onCaptureBitmap =
convertibleToBitmap(content = { StorySharableContent(story, modifier) })
}
StorySwitcher(
onSkipPrevious = onSkipPrevious,
onSkipNext = onSkipNext,
onPause = onPause,
onStart = onStart,
) {
if (story.isInteractive) {
onCaptureBitmap =
convertibleToBitmap(content = { StorySharableContent(story, modifier) })
}
}
SegmentedProgressIndicator(
progress = progress,
segmentsData = state.segmentsData,
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
)
CloseButtonView(onCloseClicked)
}
) {
state.currentStory?.let {
StoryView(it)
}
SegmentedProgressIndicator(
progress = progress,
segmentsData = state.segmentsData,
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
)
CloseButtonView(onCloseClicked)
requireNotNull(onCaptureBitmap).let {
ShareButton(
onClick = { onShareClicked.invoke(it) }
)
}
}
}

@Composable
private fun StoryView(
private fun StorySharableContent(
story: Story,
modifier: Modifier = Modifier,
modifier: Modifier,
) {
Column {
Box(
modifier = modifier
.fillMaxSize()
.weight(weight = 1f, fill = true)
.clip(RoundedCornerShape(StoryViewCornerSize))
.background(color = story.backgroundColor),
contentAlignment = Alignment.Center
) {
when (story) {
is StoryFake1 -> StoryFake1View(story)
is StoryFake2 -> StoryFake2View(story)
}
Box(
modifier = modifier
.fillMaxSize()
.clip(RoundedCornerShape(StoryViewCornerSize))
.background(color = story.backgroundColor),
contentAlignment = Alignment.Center
) {
when (story) {
is StoryFake1 -> StoryFake1View(story)
is StoryFake2 -> StoryFake2View(story)
}
ShareButton()
}
}

@Composable
private fun ShareButton() {
private fun ShareButton(
onClick: () -> Unit,
) {
RowOutlinedButton(
text = stringResource(id = LR.string.share),
border = BorderStroke(ShareButtonStrokeWidth, Color.White),
Expand All @@ -167,7 +160,9 @@ private fun ShareButton() {
contentColor = Color.White,
),
iconImage = Icons.Default.Share,
onClick = {}
onClick = {
onClick.invoke()
}
)
}

Expand Down Expand Up @@ -244,6 +239,52 @@ private fun StoriesEmptyView(
}
}

@Composable
private fun StorySwitcher(
onSkipPrevious: () -> Unit,
onSkipNext: () -> Unit,
onPause: () -> Unit,
onStart: () -> Unit,
modifier: Modifier = Modifier,
content: (@Composable () -> Unit)?,
) {
var screenWidth by remember { mutableStateOf(1) }
var isPaused by remember { mutableStateOf(false) }
Box(
modifier = modifier
.fillMaxSize()
.onGloballyPositioned {
screenWidth = it.size.width
}
.pointerInput(Unit) {
detectTapGestures(
onTap = {
if (!isPaused) {
if (it.x > screenWidth / 2) {
onSkipNext()
} else {
onSkipPrevious()
}
}
},
onLongPress = {
isPaused = true
onPause()
},
onPress = {
awaitRelease()
if (isPaused) {
onStart()
isPaused = false
}
}
)
}
) {
content?.invoke()
}
}

@Preview(showBackground = true)
@Composable
private fun StoriesScreenPreview(
Expand All @@ -263,7 +304,8 @@ private fun StoriesScreenPreview(
onSkipNext = {},
onPause = {},
onStart = {},
onCloseClicked = {}
onCloseClicked = {},
onShareClicked = {}
)
}
}
Expand Down
Loading

0 comments on commit bea60f1

Please sign in to comment.