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

Add pager to watch app #908

Merged
merged 10 commits into from
Apr 27, 2023
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
@file:OptIn(ExperimentalFoundationApi::class)

package au.com.shiftyjelly.pocketcasts.wear

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.wear.compose.material.rememberSwipeToDismissBoxState
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
import au.com.shiftyjelly.pocketcasts.wear.theme.WearAppTheme
import au.com.shiftyjelly.pocketcasts.wear.ui.FilesScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.FiltersScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.LoggingInScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.SettingsScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.UpNextScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.WatchListScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.authenticationGraph
import au.com.shiftyjelly.pocketcasts.wear.ui.authenticationSubGraph
import au.com.shiftyjelly.pocketcasts.wear.ui.component.NowPlayingPager
import au.com.shiftyjelly.pocketcasts.wear.ui.downloads.DownloadsScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.episode.EpisodeScreenFlow
import au.com.shiftyjelly.pocketcasts.wear.ui.episode.EpisodeScreenFlow.episodeGraph
import au.com.shiftyjelly.pocketcasts.wear.ui.player.NowPlayingScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.player.NowPlayingViewModel
import au.com.shiftyjelly.pocketcasts.wear.ui.player.StreamingConfirmationScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.podcast.PodcastScreen
import au.com.shiftyjelly.pocketcasts.wear.ui.podcasts.PodcastsScreen
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel
import com.google.android.horologist.compose.navscaffold.WearNavScaffold
import com.google.android.horologist.compose.navscaffold.composable
import com.google.android.horologist.compose.navscaffold.scrollable
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -53,94 +55,80 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
val state by viewModel.state.collectAsState()
WearApp(
themeType = theme.activeTheme,
signInConfirmationAction = state.signInConfirmationAction,
onSignInConfirmationActionHandled = viewModel::onSignInConfirmationActionHandled,
)
WearAppTheme(theme.activeTheme) {
WearApp(
signInConfirmationAction = state.signInConfirmationAction,
onSignInConfirmationActionHandled = viewModel::onSignInConfirmationActionHandled,
)
}
}
}
}

@Composable
fun WearApp(
themeType: Theme.ThemeType,
signInConfirmationAction: SignInConfirmationAction?,
onSignInConfirmationActionHandled: () -> Unit,
) {
WearAppTheme(themeType) {

val navController = rememberSwipeDismissableNavController()
val navController = rememberSwipeDismissableNavController()
val swipeToDismissState = rememberSwipeToDismissBoxState()
val navState = rememberSwipeDismissableNavHostState(swipeToDismissState)

WearNavScaffold(
navController = navController,
startDestination = WatchListScreen.route
) {
handleSignInConfirmation(
signInConfirmationAction = signInConfirmationAction,
onSignInConfirmationActionHandled = onSignInConfirmationActionHandled,
navController = navController
)

handleSignInConfirmation(
signInConfirmationAction = signInConfirmationAction,
onSignInConfirmationActionHandled = onSignInConfirmationActionHandled,
navController = navController
)
WearNavScaffold(
navController = navController,
startDestination = WatchListScreen.route,
state = navState,
) {

scrollable(
route = WatchListScreen.route,
columnStateFactory = ScalingLazyColumnDefaults.belowTimeText()
scrollable(
route = WatchListScreen.route,
) {
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
NowPlayingPager(
navController = navController,
pagerState = pagerState,
swipeToDismissState = swipeToDismissState,
) {
WatchListScreen(navController::navigate, it.scrollableState)
}

composable(NowPlayingScreen.route) {
it.timeTextMode = NavScaffoldViewModel.TimeTextMode.Off

// Listen for results from streaming confirmation screen
navController.currentBackStackEntry?.savedStateHandle
?.getStateFlow<StreamingConfirmationScreen.Result?>(StreamingConfirmationScreen.resultKey, null)
?.collectAsStateWithLifecycle()?.value?.let { streamingConfirmationResult ->
val viewModel = hiltViewModel<NowPlayingViewModel>()
LaunchedEffect(streamingConfirmationResult) {
viewModel.onStreamingConfirmationResult(streamingConfirmationResult)
// Clear result once consumed
navController.currentBackStackEntry?.savedStateHandle
?.remove<StreamingConfirmationScreen.Result?>(StreamingConfirmationScreen.resultKey)
WatchListScreen(
scrollState = it.scrollableState,
navigateToRoute = navController::navigate,
toNowPlaying = {
coroutineScope.launch {
pagerState.animateScrollToPage(1)
}
}

NowPlayingScreen(
navigateToEpisode = { episodeUuid ->
navController.navigate(EpisodeScreenFlow.navigateRoute(episodeUuid))
},
showStreamingConfirmation = { navController.navigate(StreamingConfirmationScreen.route) },
)
}
}

composable(StreamingConfirmationScreen.route) {
it.timeTextMode = NavScaffoldViewModel.TimeTextMode.Off
composable(StreamingConfirmationScreen.route) {
it.timeTextMode = NavScaffoldViewModel.TimeTextMode.Off

StreamingConfirmationScreen(
onFinished = { result ->
navController.previousBackStackEntry?.savedStateHandle?.set(
StreamingConfirmationScreen.resultKey,
result
)
navController.popBackStack()
},
)
}

scrollable(
route = UpNextScreen.route,
) {
UpNextScreen(
navigateToEpisode = { episodeUuid ->
navController.navigate(EpisodeScreenFlow.navigateRoute(episodeUuid))
},
listState = it.scrollableState,
)
}
StreamingConfirmationScreen(
onFinished = { result ->
navController.previousBackStackEntry?.savedStateHandle?.set(
StreamingConfirmationScreen.resultKey,
result
)
navController.popBackStack()
},
)
}

scrollable(
route = PodcastsScreen.route,
scrollable(
route = PodcastsScreen.route,
) {
NowPlayingPager(
navController = navController,
swipeToDismissState = swipeToDismissState,
) {
PodcastsScreen(
listState = it.scrollableState,
Expand All @@ -149,32 +137,52 @@ fun WearApp(
}
)
}
}

composable(
route = PodcastScreen.route,
arguments = listOf(
navArgument(PodcastScreen.argument) {
type = NavType.StringType
}
),
composable(
route = PodcastScreen.route,
arguments = listOf(
navArgument(PodcastScreen.argument) {
type = NavType.StringType
}
),
) {

NowPlayingPager(
navController = navController,
swipeToDismissState = swipeToDismissState,
) {
PodcastScreen(
onEpisodeTap = { episode ->
navController.navigate(EpisodeScreenFlow.navigateRoute(episodeUuid = episode.uuid))
},
)
}
}

episodeGraph(
navigateToPodcast = { podcastUuid ->
navController.navigate(PodcastScreen.navigateRoute(podcastUuid))
},
episodeGraph(
navigateToPodcast = { podcastUuid ->
navController.navigate(PodcastScreen.navigateRoute(podcastUuid))
},
navController = navController,
swipeToDismissState = swipeToDismissState,
)

composable(FiltersScreen.route) {
NowPlayingPager(
navController = navController,
)
swipeToDismissState = swipeToDismissState,
) {
FiltersScreen()
}
}

composable(FiltersScreen.route) { FiltersScreen() }
scrollable(DownloadsScreen.route) {

scrollable(DownloadsScreen.route) {
NowPlayingPager(
navController = navController,
swipeToDismissState = swipeToDismissState,
) {
DownloadsScreen(
columnState = it.columnState,
onItemClick = { episode ->
Expand All @@ -183,23 +191,30 @@ fun WearApp(
}
)
}
}

composable(FilesScreen.route) { FilesScreen() }

scrollable(SettingsScreen.route) {
SettingsScreen(
scrollState = it.columnState,
signInClick = { navController.navigate(authenticationSubGraph) },
)
composable(FilesScreen.route) {
NowPlayingPager(
navController = navController,
swipeToDismissState = swipeToDismissState,
) {
FilesScreen()
}
}

authenticationGraph(navController)
scrollable(SettingsScreen.route) {
SettingsScreen(
scrollState = it.columnState,
signInClick = { navController.navigate(authenticationSubGraph) },
)
}

composable(LoggingInScreen.route) {
LoggingInScreen(
onClose = { navController.popBackStack() },
)
}
authenticationGraph(navController)

composable(LoggingInScreen.route) {
LoggingInScreen(
onClose = { navController.popBackStack() },
)
}
}
}
Expand Down Expand Up @@ -236,7 +251,6 @@ private fun handleSignInConfirmation(
@Composable
fun DefaultPreview() {
WearApp(
themeType = Theme.ThemeType.DARK,
signInConfirmationAction = null,
onSignInConfirmationActionHandled = {},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ class WearMainActivityViewModel @Inject constructor(
}

/**
* This should be invoked when the UI it has handled showing or hiding the sign in
* confirmation.
* This should be invoked when the UI has handled showing or hiding the sign in confirmation.
*/
fun onSignInConfirmationActionHandled() {
_state.update { it.copy(signInConfirmationAction = null) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package au.com.shiftyjelly.pocketcasts.wear.ui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand All @@ -15,10 +18,21 @@ object FilesScreen {

@Composable
fun FilesScreen() {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.primary,
text = stringResource(LR.string.profile_navigation_files)
)
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.primary,
text = stringResource(LR.string.profile_navigation_files)
)
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.primary.copy(alpha = 0.5f),
text = "placeholder screen"
)
}
}
Loading