Skip to content

Commit

Permalink
Store playback position (in-memory).
Browse files Browse the repository at this point in the history
  • Loading branch information
ychescale9 committed Feb 26, 2024
1 parent 811f201 commit 78f5048
Show file tree
Hide file tree
Showing 11 changed files with 65 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -64,35 +64,36 @@ public fun TalkingKotlinEpisodeScreen(
modifier: Modifier = Modifier,
) {
val viewModel = viewModel<TalkingKotlinEpisodeViewModel>()
LaunchedEffect(id) {
DisposableEffect(Unit) {
viewModel.loadTalkingKotlinEpisode(id)
onDispose {
viewModel.reset()
}
}
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
val context = LocalContext.current
TalkingKotlinEpisodeScreen(
title = "",
onNavigateUp = onNavigateUp,
onSaveButtonClick = { /* TODO */ },
onShareButtonClick = { title, url ->
context.openShareSheet(title, url)
},
onPlayPauseButtonClick = { viewModel.togglePlayPause() },
onOpenLink = {
context.openCustomTab(it)
},
onPlayPauseButtonClick = viewModel::togglePlayPause,
onOpenLink = context::openCustomTab,
onSaveStartPosition = viewModel::saveStartPosition,
uiState = uiState,
modifier = modifier,
)
}

@Composable
internal fun TalkingKotlinEpisodeScreen(
title: String,
onNavigateUp: () -> Unit,
onShareButtonClick: (title: String, url: String) -> Unit,
onSaveButtonClick: () -> Unit,
onPlayPauseButtonClick: () -> Unit,
onOpenLink: (url: String) -> Unit,
onSaveStartPosition: (startPositionMillis: Long) -> Unit,
uiState: TalkingKotlinEpisodeUiState,
modifier: Modifier = Modifier,
) {
Expand All @@ -102,7 +103,7 @@ internal fun TalkingKotlinEpisodeScreen(
.background(KSTheme.colorScheme.background),
) {
TopNavBar(
title = title,
title = "",
modifier = Modifier.zIndex(1f),
contentPadding = WindowInsets.statusBars.asPaddingValues(),
navigationIcon = {
Expand Down Expand Up @@ -153,6 +154,7 @@ internal fun TalkingKotlinEpisodeScreen(
isPlaying = uiState.isPlaying,
onPlayPauseButtonClick = onPlayPauseButtonClick,
onOpenLink = onOpenLink,
onSaveStartPosition = onSaveStartPosition,
)
}
}
Expand All @@ -166,6 +168,7 @@ private fun ContentUi(
isPlaying: Boolean,
onPlayPauseButtonClick: () -> Unit,
onOpenLink: (url: String) -> Unit,
onSaveStartPosition: (startPositionMillis: Long) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
Expand Down Expand Up @@ -271,6 +274,7 @@ private fun ContentUi(
episode = episode,
isPlaying = isPlaying,
onPlayPauseButtonClick = onPlayPauseButtonClick,
onSaveStartPosition = onSaveStartPosition,
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@ internal class TalkingKotlinEpisodeViewModel @Inject constructor(
presenter.loadTalkingKotlinEpisode(id)
}

fun saveStartPosition(startPositionMillis: Long) = viewModelScope.launch {
presenter.saveStartPosition(startPositionMillis)
}

fun togglePlayPause() = presenter.togglePlayPause()

fun reset() = presenter.reset()
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ internal fun PodcastPlayer(
episode: TalkingKotlinEpisode,
isPlaying: Boolean,
onPlayPauseButtonClick: () -> Unit,
onSaveStartPosition: (Long) -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
) {
Expand Down Expand Up @@ -107,9 +108,9 @@ internal fun PodcastPlayer(
}

DisposableEffect(Unit) {
@Suppress("MagicNumber")
player.seekTo(1200_000)
player.seekTo(episode.startPositionMillis)
onDispose {
onSaveStartPosition(playerPositionMillis.toLong())
player.release()
}
}
Expand Down Expand Up @@ -257,6 +258,7 @@ private fun PreviewPodcastPlayerUi_paused() {
thumbnailUrl = "podcast-logo-url",
summary = "summary",
duration = "35min.",
startPositionMillis = 0,
),
isPlaying = false,
onPlayPauseButtonClick = {},
Expand Down Expand Up @@ -285,6 +287,7 @@ private fun PreviewPodcastPlayer_playing() {
thumbnailUrl = "podcast-logo-url",
summary = "summary",
duration = "35min.",
startPositionMillis = 0,
),
isPlaying = true,
onPlayPauseButtonClick = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ public class FeedRepository(
TODO()
}

@Suppress("MaxLineLength")
private var fakeTalkingKotlinEpisode = FeedItem.TalkingKotlin(
id = "tag:soundcloud,2010:tracks/1689535512",
title = "Coil Goes Multiplatform with Colin White",
publishTime = "2023-11-29T22:00:00Z".toInstant(),
contentUrl = "https://soundcloud.com/user-38099918/coil-goes-multiplatform-with-colin-white",
savedForLater = false,
audioUrl = "https://feeds.soundcloud.com/stream/1689535512-user-38099918-network-resilient-applications-with-store5-talking-kotlin-128.mp3",
thumbnailUrl = "https://talkingkotlin.com/images/kotlin_talking_logo.png",
summary = "Welcome to another engaging episode of Talking Kotlin! In this edition, we dive into the dynamic world of Android development with Colin White, the creator of the widely acclaimed Coil library. Join us as we discuss the latest developments, insights, and the exciting roadmap for Coil. \uD83D\uDE80 Highlights from this Episode: Learn about Colin's journey in developing the Coil library. Discover the pivotal role Coil plays in simplifying image loading for Android developers. Get an exclusive sneak peek into the upcoming Coil 3.0, featuring multi-platform support and seamless integration with Jetpack Compose. \uD83D\uDD17 Helpful Links: Coil Library GitHub: github.com/coilkt/coil Follow Colin White on Twitter: @colinwhi \uD83C\uDF10 Connect with the Kotlin Community: https://kotlinlang.org/community/ Kotlin Foundation: https://kotlinfoundation.org/",
duration = "42min.",
startPositionMillis = 0,
)

public suspend fun loadFeedItemById(id: String): FeedItem? {
// TODO load from DB
return when {
Expand All @@ -48,24 +62,17 @@ public class FeedRepository(
issueNumber = 386,
)
}
id.contains("tag:soundcloud") -> {
@Suppress("MaxLineLength")
FeedItem.TalkingKotlin(
id = id,
title = "Coil Goes Multiplatform with Colin White",
publishTime = "2023-11-29T22:00:00Z".toInstant(),
contentUrl = "https://soundcloud.com/user-38099918/coil-goes-multiplatform-with-colin-white",
savedForLater = false,
audioUrl = "https://feeds.soundcloud.com/stream/1689535512-user-38099918-network-resilient-applications-with-store5-talking-kotlin-128.mp3",
thumbnailUrl = "https://talkingkotlin.com/images/kotlin_talking_logo.png",
summary = "Welcome to another engaging episode of Talking Kotlin! In this edition, we dive into the dynamic world of Android development with Colin White, the creator of the widely acclaimed Coil library. Join us as we discuss the latest developments, insights, and the exciting roadmap for Coil. \uD83D\uDE80 Highlights from this Episode: Learn about Colin's journey in developing the Coil library. Discover the pivotal role Coil plays in simplifying image loading for Android developers. Get an exclusive sneak peek into the upcoming Coil 3.0, featuring multi-platform support and seamless integration with Jetpack Compose. \uD83D\uDD17 Helpful Links: Coil Library GitHub: github.com/coilkt/coil Follow Colin White on Twitter: @colinwhi \uD83C\uDF10 Connect with the Kotlin Community: https://kotlinlang.org/community/ Kotlin Foundation: https://kotlinfoundation.org/",
duration = "42min.",
)
}
id.contains("tag:soundcloud") -> fakeTalkingKotlinEpisode
else -> null
}
}

public suspend fun saveTalkingKotlinEpisodeStartPosition(id: String, positionMillis: Long) {
// TODO persist to DB
require(id == fakeTalkingKotlinEpisode.id)
fakeTalkingKotlinEpisode = fakeTalkingKotlinEpisode.copy(startPositionMillis = positionMillis)
}

public suspend fun loadSavedFeedItems(): List<FeedItem> {
TODO()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public sealed interface FeedItem {
val thumbnailUrl: String,
val summary: String,
val duration: String,
val startPositionMillis: Long,
) : FeedItem

public data class KotlinWeekly(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public val FakeHomeFeedItems: List<HomeFeedItem> = listOf(
thumbnailUrl = "https://i1.sndcdn.com/artworks-uP9Cxy5KSYNzGebf-3q1MsQ-t3000x3000.jpg",
summary = "Dive into the intricate world of microservices with the latest episode of Talking Kotlin, hosted by Sebastian and Hadi.",
duration = "56min.",
startPositionMillis = 0,
),
FeedItem.KotlinBlog(
id = "https://blog.jetbrains.com/?post_type=blog&p=404245",
Expand Down Expand Up @@ -134,5 +135,6 @@ public val FakeHomeFeedItems: List<HomeFeedItem> = listOf(
thumbnailUrl = "https://i1.sndcdn.com/avatars-000289370353-di6ese-original.jpg",
summary = "In this episode, we are talking to engineers from @instaboxglobal who use Compose Multiplatform in Production.",
duration = "55min.",
startPositionMillis = 0,
),
).toHomeFeedItems()
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class HomeFeedItemMapperTest {
thumbnailUrl = "podcast-logo-url",
summary = "summary",
duration = "35min.",
startPositionMillis = 0,
),
// 5 days ago
FeedItem.KotlinBlog(
Expand Down Expand Up @@ -148,6 +149,7 @@ class HomeFeedItemMapperTest {
thumbnailUrl = "podcast-logo-url",
summary = "summary",
duration = "35min.",
startPositionMillis = 0,
),
displayablePublishTime = "Yesterday",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public data class TalkingKotlinEpisode(
val thumbnailUrl: String,
val summary: String,
val duration: String,
val startPositionMillis: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal fun FeedItem.TalkingKotlin.asPresentationModel(
audioUrl = audioUrl,
thumbnailUrl = thumbnailUrl,
summary = summary,
duration = duration
duration = duration,
startPositionMillis = startPositionMillis,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public class TalkingKotlinEpisodePresenter(
)
}

public suspend fun saveStartPosition(startPositionMillis: Long) {
val id = (uiState.value as TalkingKotlinEpisodeUiState.Content).episode.id
feedRepository.saveTalkingKotlinEpisodeStartPosition(id, startPositionMillis)
}

public fun togglePlayPause() {
_uiState.update {
if (it is TalkingKotlinEpisodeUiState.Content) {
Expand All @@ -33,4 +38,9 @@ public class TalkingKotlinEpisodePresenter(
}
}
}

// TODO remove once ViewModel is scoped properly
public fun reset() {
_uiState.value = TalkingKotlinEpisodeUiState.Initializing
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class TalkingKotlinEpisodeMapperTest {
thumbnailUrl = "podcast-logo-url",
summary = "summary",
duration = "35min.",
startPositionMillis = 3000,
)

val expectedTalkingKotlinEpisode = TalkingKotlinEpisode(
Expand All @@ -34,6 +35,7 @@ class TalkingKotlinEpisodeMapperTest {
thumbnailUrl = "podcast-logo-url",
summary = "summary",
duration = "35min.",
startPositionMillis = 3000,
)

assertEquals(expectedTalkingKotlinEpisode, feedItem.asPresentationModel(timeZone))
Expand Down

0 comments on commit 78f5048

Please sign in to comment.