Skip to content

Commit 03a6537

Browse files
authored
Fix playback position saving when navigating back. (#477)
1 parent fd53f01 commit 03a6537

File tree

12 files changed

+61
-39
lines changed

12 files changed

+61
-39
lines changed

android/feature/content-viewer/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/contentviewer/impl/ContentViewerEntryProvider.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public fun EntryProviderScope<NavKey>.contentViewerEntry(
1616
entry<ContentViewerRoute> {
1717
ContentViewerScreen(
1818
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
19+
backStack = backStack,
1920
boundsKey = ContentViewerSharedTransitionKeys.bounds(
2021
origin = it.origin,
2122
id = it.id,
@@ -26,9 +27,6 @@ public fun EntryProviderScope<NavKey>.contentViewerEntry(
2627
id = it.id,
2728
),
2829
id = it.id,
29-
onNavigateUp = {
30-
backStack.removeLastOrNull()
31-
},
3230
)
3331
}
3432
}

android/feature/content-viewer/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/contentviewer/impl/ContentViewerScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import androidx.compose.ui.platform.LocalContext
3939
import androidx.compose.ui.unit.dp
4040
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
4141
import androidx.lifecycle.compose.collectAsStateWithLifecycle
42+
import androidx.navigation3.runtime.NavBackStack
43+
import androidx.navigation3.runtime.NavKey
4244
import androidx.tracing.trace
4345
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.FilledIconButton
4446
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.LargeIconButton
@@ -55,11 +57,11 @@ import kotlinx.coroutines.delay
5557
@Composable
5658
internal fun SharedTransitionScope.ContentViewerScreen(
5759
animatedVisibilityScope: AnimatedVisibilityScope,
60+
backStack: NavBackStack<NavKey>,
5861
boundsKey: String,
5962
topBarBoundsKey: String,
6063
saveButtonElementKey: String,
6164
id: String,
62-
onNavigateUp: () -> Unit,
6365
modifier: Modifier = Modifier,
6466
) = trace("Screen:ContentViewer") {
6567
val viewModel = hiltViewModel<ContentViewerViewModel, ContentViewerViewModel.Factory>(
@@ -88,7 +90,7 @@ internal fun SharedTransitionScope.ContentViewerScreen(
8890
LargeIconButton(
8991
KSIcons.Close,
9092
contentDescription = null,
91-
onClick = onNavigateUp,
93+
onClick = backStack::removeLastOrNull,
9294
)
9395
},
9496
actions = {

android/feature/kotlin-weekly-issue/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/kotlinweeklyissue/impl/KotlinWeeklyIssueEntryProvider.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public fun EntryProviderScope<NavKey>.kotlinWeeklyIssueEntry(
1616
entry<KotlinWeeklyIssueRoute> {
1717
KotlinWeeklyIssueScreen(
1818
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
19+
backStack = backStack,
1920
boundsKey = KotlinWeeklyIssueSharedTransitionKeys.bounds(
2021
origin = it.origin,
2122
id = it.id,
@@ -24,9 +25,6 @@ public fun EntryProviderScope<NavKey>.kotlinWeeklyIssueEntry(
2425
titleElementKey = TopNavBarSharedTransitionKeys.titleElement(it.origin),
2526
id = it.id,
2627
issueNumber = it.issueNumber,
27-
onNavigateUp = {
28-
backStack.removeLastOrNull()
29-
},
3028
)
3129
}
3230
}

android/feature/kotlin-weekly-issue/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/kotlinweeklyissue/impl/KotlinWeeklyIssueScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import androidx.compose.ui.unit.dp
3939
import androidx.compose.ui.zIndex
4040
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
4141
import androidx.lifecycle.compose.collectAsStateWithLifecycle
42+
import androidx.navigation3.runtime.NavBackStack
43+
import androidx.navigation3.runtime.NavKey
4244
import androidx.tracing.trace
4345
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.FilledIconButton
4446
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.LargeIconButton
@@ -58,12 +60,12 @@ import io.github.reactivecircus.kstreamlined.kmp.presentation.kotlinweeklyissue.
5860
@Composable
5961
internal fun SharedTransitionScope.KotlinWeeklyIssueScreen(
6062
animatedVisibilityScope: AnimatedVisibilityScope,
63+
backStack: NavBackStack<NavKey>,
6164
boundsKey: String,
6265
topBarBoundsKey: String,
6366
titleElementKey: String,
6467
id: String,
6568
issueNumber: Int,
66-
onNavigateUp: () -> Unit,
6769
modifier: Modifier = Modifier,
6870
) = trace("Screen:KotlinWeeklyIssue") {
6971
val viewModel = hiltViewModel<KotlinWeeklyIssueViewModel, KotlinWeeklyIssueViewModel.Factory>(
@@ -79,7 +81,7 @@ internal fun SharedTransitionScope.KotlinWeeklyIssueScreen(
7981
topBarBoundsKey = topBarBoundsKey,
8082
titleElementKey = titleElementKey,
8183
title = title,
82-
onNavigateUp = onNavigateUp,
84+
onNavigateUp = backStack::removeLastOrNull,
8385
onShareButtonClick = { url ->
8486
context.openShareSheet(title, url)
8587
},

android/feature/licenses/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/licenses/impl/LicensesEntryProvider.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ public fun EntryProviderScope<NavKey>.licensesEntry(
1414
entry<LicensesRoute> {
1515
LicensesScreen(
1616
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
17-
onNavigateUp = {
18-
backStack.removeLastOrNull()
19-
},
17+
backStack = backStack,
2018
)
2119
}
2220
}

android/feature/licenses/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/licenses/impl/LicensesScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import androidx.compose.ui.res.stringResource
2323
import androidx.compose.ui.zIndex
2424
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
2525
import androidx.lifecycle.compose.collectAsStateWithLifecycle
26+
import androidx.navigation3.runtime.NavBackStack
27+
import androidx.navigation3.runtime.NavKey
2628
import androidx.tracing.trace
2729
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.LargeIconButton
2830
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.TopNavBar
@@ -34,14 +36,14 @@ import io.github.reactivecircus.kstreamlined.android.feature.licenses.impl.compo
3436
@Composable
3537
internal fun SharedTransitionScope.LicensesScreen(
3638
animatedVisibilityScope: AnimatedVisibilityScope,
37-
onNavigateUp: () -> Unit,
39+
backStack: NavBackStack<NavKey>,
3840
modifier: Modifier = Modifier,
3941
) = trace("Screen:Licenses") {
4042
val viewModel = hiltViewModel<LicensesViewModel>()
4143
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
4244

4345
LicensesScreen(
44-
onNavigateUp = onNavigateUp,
46+
onNavigateUp = backStack::removeLastOrNull,
4547
uiState = uiState,
4648
modifier = modifier.sharedBounds(
4749
sharedContentState = rememberSharedContentState(key = LicensesSharedTransitionKeys.Bounds),

android/feature/settings/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/settings/impl/SettingsEntryProvider.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.navigation3.runtime.NavBackStack
66
import androidx.navigation3.runtime.NavKey
77
import androidx.navigation3.ui.LocalNavAnimatedContentScope
88
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.TopNavBarSharedTransitionKeys
9-
import io.github.reactivecircus.kstreamlined.android.feature.licenses.api.LicensesRoute
109
import io.github.reactivecircus.kstreamlined.android.feature.settings.api.SettingsRoute
1110

1211
public fun EntryProviderScope<NavKey>.settingsEntry(
@@ -16,14 +15,9 @@ public fun EntryProviderScope<NavKey>.settingsEntry(
1615
entry<SettingsRoute> {
1716
SettingsScreen(
1817
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
18+
backStack = backStack,
1919
topBarBoundsKey = TopNavBarSharedTransitionKeys.bounds(it.origin),
2020
titleElementKey = TopNavBarSharedTransitionKeys.titleElement(it.origin),
21-
onOpenLicenses = {
22-
backStack.add(LicensesRoute)
23-
},
24-
onNavigateUp = {
25-
backStack.removeLastOrNull()
26-
},
2721
)
2822
}
2923
}

android/feature/settings/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/settings/impl/SettingsScreen.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import androidx.compose.ui.unit.dp
3535
import androidx.compose.ui.zIndex
3636
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
3737
import androidx.lifecycle.compose.collectAsStateWithLifecycle
38+
import androidx.navigation3.runtime.NavBackStack
39+
import androidx.navigation3.runtime.NavKey
3840
import androidx.tracing.trace
3941
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.LargeIconButton
4042
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.ModalBottomSheet
@@ -43,6 +45,7 @@ import io.github.reactivecircus.kstreamlined.android.core.designsystem.component
4345
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.rememberModalBottomSheetState
4446
import io.github.reactivecircus.kstreamlined.android.core.designsystem.foundation.KSTheme
4547
import io.github.reactivecircus.kstreamlined.android.core.designsystem.foundation.icon.KSIcons
48+
import io.github.reactivecircus.kstreamlined.android.feature.licenses.api.LicensesRoute
4649
import io.github.reactivecircus.kstreamlined.android.feature.licenses.api.LicensesSharedTransitionKeys
4750
import io.github.reactivecircus.kstreamlined.android.feature.settings.impl.component.about.OpenSourceLicensesTile
4851
import io.github.reactivecircus.kstreamlined.android.feature.settings.impl.component.about.SourceCodeTile
@@ -58,10 +61,9 @@ import kotlinx.coroutines.launch
5861
@Composable
5962
internal fun SharedTransitionScope.SettingsScreen(
6063
animatedVisibilityScope: AnimatedVisibilityScope,
64+
backStack: NavBackStack<NavKey>,
6165
topBarBoundsKey: String,
6266
titleElementKey: String,
63-
onOpenLicenses: () -> Unit,
64-
onNavigateUp: () -> Unit,
6567
modifier: Modifier = Modifier,
6668
) = trace("Screen:Settings") {
6769
val viewModel = hiltViewModel<SettingsViewModel>()
@@ -72,8 +74,8 @@ internal fun SharedTransitionScope.SettingsScreen(
7274
animatedVisibilityScope = animatedVisibilityScope,
7375
topBarBoundsKey = topBarBoundsKey,
7476
titleElementKey = titleElementKey,
75-
onOpenLicenses = onOpenLicenses,
76-
onNavigateUp = onNavigateUp,
77+
onOpenLicenses = { backStack.add(LicensesRoute) },
78+
onNavigateUp = backStack::removeLastOrNull,
7779
uiState = uiState,
7880
eventSink = eventSink,
7981
modifier = modifier,

android/feature/talking-kotlin-episode/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/talkingkotlinepisode/impl/TalkingKotlinEpisodeEntryProvider.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public fun EntryProviderScope<NavKey>.talkingKotlinEpisodeEntry(
1616
entry<TalkingKotlinEpisodeRoute> {
1717
TalkingKotlinEpisodeScreen(
1818
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
19+
backStack = backStack,
1920
boundsKey = TalkingKotlinEpisodeSharedTransitionKeys.bounds(
2021
origin = it.origin,
2122
id = it.id,
@@ -26,9 +27,6 @@ public fun EntryProviderScope<NavKey>.talkingKotlinEpisodeEntry(
2627
id = it.id,
2728
),
2829
id = it.id,
29-
onNavigateUp = {
30-
backStack.removeLastOrNull()
31-
},
3230
)
3331
}
3432
}

android/feature/talking-kotlin-episode/impl/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/feature/talkingkotlinepisode/impl/TalkingKotlinEpisodeScreen.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ import androidx.compose.foundation.layout.windowInsetsPadding
2424
import androidx.compose.foundation.lazy.LazyColumn
2525
import androidx.compose.foundation.shape.RoundedCornerShape
2626
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.LaunchedEffect
2728
import androidx.compose.runtime.getValue
29+
import androidx.compose.runtime.mutableLongStateOf
2830
import androidx.compose.runtime.remember
31+
import androidx.compose.runtime.setValue
2932
import androidx.compose.ui.Alignment
3033
import androidx.compose.ui.Modifier
3134
import androidx.compose.ui.draw.clip
@@ -41,6 +44,8 @@ import androidx.compose.ui.unit.dp
4144
import androidx.compose.ui.zIndex
4245
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
4346
import androidx.lifecycle.compose.collectAsStateWithLifecycle
47+
import androidx.navigation3.runtime.NavBackStack
48+
import androidx.navigation3.runtime.NavKey
4449
import androidx.tracing.trace
4550
import coil3.compose.AsyncImage
4651
import io.github.reactivecircus.kstreamlined.android.core.designsystem.component.FilledIconButton
@@ -56,6 +61,7 @@ import io.github.reactivecircus.kstreamlined.android.core.launcher.openCustomTab
5661
import io.github.reactivecircus.kstreamlined.android.core.launcher.openShareSheet
5762
import io.github.reactivecircus.kstreamlined.android.core.ui.pattern.ItemNotFoundUi
5863
import io.github.reactivecircus.kstreamlined.android.core.ui.util.linkify
64+
import io.github.reactivecircus.kstreamlined.android.feature.talkingkotlinepisode.api.TalkingKotlinEpisodeRoute
5965
import io.github.reactivecircus.kstreamlined.android.feature.talkingkotlinepisode.impl.component.PlayPauseButton
6066
import io.github.reactivecircus.kstreamlined.android.feature.talkingkotlinepisode.impl.component.PodcastPlayer
6167
import io.github.reactivecircus.kstreamlined.kmp.presentation.talkingkotlinepisode.TalkingKotlinEpisode
@@ -65,11 +71,11 @@ import io.github.reactivecircus.kstreamlined.kmp.presentation.talkingkotlinepiso
6571
@Composable
6672
internal fun SharedTransitionScope.TalkingKotlinEpisodeScreen(
6773
animatedVisibilityScope: AnimatedVisibilityScope,
74+
backStack: NavBackStack<NavKey>,
6875
topBarBoundsKey: String,
6976
boundsKey: String,
7077
playerElementKey: String,
7178
id: String,
72-
onNavigateUp: () -> Unit,
7379
modifier: Modifier = Modifier,
7480
) = trace("Screen:TalkingKotlinEpisode") {
7581
val viewModel = hiltViewModel<TalkingKotlinEpisodeViewModel, TalkingKotlinEpisodeViewModel.Factory>(
@@ -79,22 +85,33 @@ internal fun SharedTransitionScope.TalkingKotlinEpisodeScreen(
7985
val eventSink = viewModel.eventSink
8086

8187
val context = LocalContext.current
88+
var playerPosition by remember { mutableLongStateOf(0L) }
89+
8290
TalkingKotlinEpisodeScreen(
8391
animatedVisibilityScope = animatedVisibilityScope,
8492
topBarBoundsKey = topBarBoundsKey,
8593
playerElementKey = playerElementKey,
86-
onNavigateUp = onNavigateUp,
94+
onNavigateUp = backStack::removeLastOrNull,
8795
onShareButtonClick = { title, url ->
8896
context.openShareSheet(title, url)
8997
},
9098
onOpenLink = context::openCustomTab,
99+
onPlayerPositionChange = { playerPosition = it },
91100
uiState = uiState,
92101
eventSink = eventSink,
93102
modifier = modifier.sharedBounds(
94103
sharedContentState = rememberSharedContentState(key = boundsKey),
95104
animatedVisibilityScope = animatedVisibilityScope,
96105
),
97106
)
107+
108+
// save playback position when navigating away
109+
val topEntry = backStack.lastOrNull()
110+
LaunchedEffect(topEntry) {
111+
if (topEntry !is TalkingKotlinEpisodeRoute) {
112+
eventSink(TalkingKotlinEpisodeUiEvent.SaveStartPosition(playerPosition))
113+
}
114+
}
98115
}
99116

100117
@Composable
@@ -104,6 +121,7 @@ internal fun SharedTransitionScope.TalkingKotlinEpisodeScreen(
104121
onNavigateUp: () -> Unit,
105122
onShareButtonClick: (title: String, url: String) -> Unit,
106123
onOpenLink: (url: String) -> Unit,
124+
onPlayerPositionChange: (Long) -> Unit,
107125
uiState: TalkingKotlinEpisodeUiState,
108126
eventSink: (TalkingKotlinEpisodeUiEvent) -> Unit,
109127
modifier: Modifier = Modifier,
@@ -171,6 +189,7 @@ internal fun SharedTransitionScope.TalkingKotlinEpisodeScreen(
171189
eventSink = eventSink,
172190
isPlaying = uiState.isPlaying,
173191
onOpenLink = onOpenLink,
192+
onPlayerPositionChange = onPlayerPositionChange,
174193
)
175194
}
176195
}
@@ -186,6 +205,7 @@ private fun SharedTransitionScope.ContentUi(
186205
eventSink: (TalkingKotlinEpisodeUiEvent) -> Unit,
187206
isPlaying: Boolean,
188207
onOpenLink: (url: String) -> Unit,
208+
onPlayerPositionChange: (Long) -> Unit,
189209
modifier: Modifier = Modifier,
190210
) {
191211
Column(
@@ -292,9 +312,7 @@ private fun SharedTransitionScope.ContentUi(
292312
episode = episode,
293313
isPlaying = isPlaying,
294314
onPlayPauseButtonClick = { eventSink(TalkingKotlinEpisodeUiEvent.TogglePlayPause) },
295-
onSaveStartPosition = { startPositionMillis ->
296-
eventSink(TalkingKotlinEpisodeUiEvent.SaveStartPosition(startPositionMillis))
297-
},
315+
onPlayerPositionChange = onPlayerPositionChange,
298316
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
299317
modifier = if (animatedVisibilityScope != null) {
300318
Modifier.sharedElement(

0 commit comments

Comments
 (0)