Skip to content

Commit

Permalink
Add a menu to change environment in "Lists" on mobile (#358)
Browse files Browse the repository at this point in the history
Co-authored-by: Joaquim Stähli <[email protected]>
  • Loading branch information
MGaetan89 and StaehliJ authored Dec 6, 2023
1 parent cca0b71 commit c54e769
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,26 @@ import java.net.URL
*/
object PlayerModule {

private fun provideIntegrationLayerItemSource(context: Context): MediaCompositionMediaItemSource =
private fun provideIntegrationLayerItemSource(context: Context, ilHost: URL = IlHost.DEFAULT): MediaCompositionMediaItemSource =
MediaCompositionMediaItemSource(
mediaCompositionDataSource = DefaultMediaCompositionDataSource(vector = context.getVector()),
mediaCompositionDataSource = DefaultMediaCompositionDataSource(vector = context.getVector(), baseUrl = ilHost),
)

/**
* Provide mixed item source that load Url and Urn
*/
fun provideMixedItemSource(context: Context): MixedMediaItemSource = MixedMediaItemSource(
provideIntegrationLayerItemSource(context)
fun provideMixedItemSource(
context: Context,
ilHost: URL = IlHost.DEFAULT
): MixedMediaItemSource = MixedMediaItemSource(
provideIntegrationLayerItemSource(context, ilHost)
)

/**
* Provide default player that allow to play urls and urns content from the SRG
*/
fun provideDefaultPlayer(context: Context): PillarboxPlayer {
return DefaultPillarbox(context = context, mediaItemSource = provideMixedItemSource(context))
fun provideDefaultPlayer(context: Context, ilHost: URL = IlHost.DEFAULT): PillarboxPlayer {
return DefaultPillarbox(context = context, mediaItemSource = provideMixedItemSource(context, ilHost))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ package ch.srgssr.pillarbox.demo.ui
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuDefaults
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
Expand All @@ -18,10 +24,16 @@ import androidx.compose.material3.TopAppBar
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
Expand All @@ -42,7 +54,9 @@ import androidx.navigation.navigation
import ch.srg.dataProvider.integrationlayer.request.image.ImageWidth
import ch.srg.dataProvider.integrationlayer.request.image.decorated
import ch.srgssr.pillarbox.analytics.SRGAnalytics
import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost
import ch.srgssr.pillarbox.demo.DemoPageView
import ch.srgssr.pillarbox.demo.R
import ch.srgssr.pillarbox.demo.shared.data.DemoItem
import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
import ch.srgssr.pillarbox.demo.shared.ui.HomeDestination
Expand All @@ -54,10 +68,13 @@ import ch.srgssr.pillarbox.demo.ui.integrationLayer.SearchView
import ch.srgssr.pillarbox.demo.ui.integrationLayer.listNavGraph
import ch.srgssr.pillarbox.demo.ui.player.SimplePlayerActivity
import ch.srgssr.pillarbox.demo.ui.showcases.showCasesNavGraph
import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme
import ch.srgssr.pillarbox.demo.ui.theme.paddings
import java.net.URL

private val bottomNavItems = listOf(HomeDestination.Examples, HomeDestination.ShowCases, HomeDestination.Lists, HomeDestination.Search)
private val topLevelRoutes =
listOf(HomeDestination.Examples.route, NavigationRoutes.showcaseList, NavigationRoutes.contentLists, HomeDestination.Search)
listOf(HomeDestination.Examples.route, NavigationRoutes.showcaseList, NavigationRoutes.contentLists, HomeDestination.Search.route)

/**
* Main view with all the navigation
Expand All @@ -69,6 +86,9 @@ fun MainNavigation() {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination

var ilHost by remember { mutableStateOf(IlHost.DEFAULT) }

Scaffold(
topBar = {
TopAppBar(
Expand All @@ -88,6 +108,14 @@ fun MainNavigation() {
}
}
}
},
actions = {
if (currentDestination?.route == NavigationRoutes.contentLists) {
ListsMenu(
currentServer = ilHost,
onServerSelected = { ilHost = it }
)
}
}
)
},
Expand All @@ -96,9 +124,7 @@ fun MainNavigation() {
}
) { innerPadding ->
val context = LocalContext.current
val ilRepository = remember {
PlayerModule.createIlRepository(context)
}

NavHost(navController = navController, startDestination = HomeDestination.Examples.route, modifier = Modifier.padding(innerPadding)) {
composable(HomeDestination.Examples.route, DemoPageView("home", listOf("app", "pillarbox", "examples"))) {
ExamplesHome()
Expand All @@ -109,14 +135,17 @@ fun MainNavigation() {
}

navigation(startDestination = NavigationRoutes.contentLists, route = HomeDestination.Lists.route) {
listNavGraph(navController, ilRepository)
val ilRepository = PlayerModule.createIlRepository(context, ilHost)

listNavGraph(navController, ilRepository, ilHost)
}

composable(HomeDestination.Info.route, DemoPageView("home", listOf("app", "pillarbox", "information"))) {
InfoView()
}

composable(route = NavigationRoutes.searchHome, DemoPageView("home", listOf("app", "pillarbox", "search"))) {
val ilRepository = PlayerModule.createIlRepository(context)
val viewModel: SearchViewModel = viewModel(factory = SearchViewModel.Factory(ilRepository))
SearchView(searchViewModel = viewModel) {
val item = DemoItem(
Expand All @@ -133,6 +162,65 @@ fun MainNavigation() {
}
}

@Composable
private fun ListsMenu(
currentServer: URL,
onServerSelected: (server: URL) -> Unit
) {
var isMenuVisible by remember { mutableStateOf(false) }

IconButton(onClick = { isMenuVisible = true }) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = stringResource(R.string.server)
)
}

DropdownMenu(
expanded = isMenuVisible,
onDismissRequest = { isMenuVisible = false },
offset = DpOffset(
x = MaterialTheme.paddings.small,
y = 0.dp,
),
) {
val currentServerUrl = currentServer.toString()
val servers = mapOf(
stringResource(R.string.production) to IlHost.PROD.toString(),
stringResource(R.string.stage) to IlHost.STAGE.toString(),
stringResource(R.string.test) to IlHost.TEST.toString()
)

Text(
text = stringResource(R.string.server),
modifier = Modifier
.padding(MenuDefaults.DropdownMenuItemContentPadding)
.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.labelMedium
)

servers.forEach { (name, url) ->
DropdownMenuItem(
text = { Text(text = name) },
onClick = {
onServerSelected(URL(url))
isMenuVisible = false
},
trailingIcon = if (currentServerUrl == url) {
{
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
}
} else {
null
}
)
}
}
}

@Composable
private fun DemoBottomNavigation(navController: NavController, currentDestination: NavDestination?) {
NavigationBar {
Expand Down Expand Up @@ -163,6 +251,28 @@ private fun DemoBottomNavigation(navController: NavController, currentDestinatio
}
}

@Composable
@Preview(showBackground = true)
private fun ListsMenuPreview() {
PillarboxTheme {
ListsMenu(
currentServer = IlHost.PROD,
onServerSelected = {}
)
}
}

@Composable
@Preview(showBackground = true)
private fun DemoBottomNavigationPreview() {
PillarboxTheme {
DemoBottomNavigation(
navController = rememberNavController(),
currentDestination = null
)
}
}

private fun NavDestination.getLabelResId(): Int {
val routes = hierarchy.map { it.route }
val navItem: HomeDestination? = bottomNavItems.firstOrNull { it.route in routes }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ import ch.srgssr.pillarbox.demo.ui.composable
import ch.srgssr.pillarbox.demo.ui.player.SimplePlayerActivity
import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme
import ch.srgssr.pillarbox.demo.ui.theme.paddings
import java.net.URL

private val defaultListsLevels = listOf("app", "pillarbox", "lists")

/**
* Build Navigation for integration layer list view
*/
fun NavGraphBuilder.listNavGraph(navController: NavController, ilRepository: ILRepository) {
fun NavGraphBuilder.listNavGraph(navController: NavController, ilRepository: ILRepository, ilHost: URL) {
val contentClick = { contentList: ContentList, content: Content ->
when (content) {
is Content.Show -> {
Expand All @@ -65,7 +66,7 @@ fun NavGraphBuilder.listNavGraph(navController: NavController, ilRepository: ILR

is Content.Media -> {
val item = DemoItem(title = content.title, uri = content.urn)
SimplePlayerActivity.startActivity(navController.context, item)
SimplePlayerActivity.startActivity(navController.context, item, ilHost)
}

is Content.Channel -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import android.os.Bundle
import android.os.IBinder
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
Expand All @@ -24,10 +23,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.media3.common.Player
import ch.srgssr.pillarbox.analytics.SRGAnalytics
import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost
import ch.srgssr.pillarbox.demo.DemoPageView
import ch.srgssr.pillarbox.demo.service.DemoPlaybackService
import ch.srgssr.pillarbox.demo.shared.data.DemoItem
Expand All @@ -37,6 +38,7 @@ import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme
import ch.srgssr.pillarbox.player.service.PlaybackService
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.net.URL

/**
* Simple player activity that can handle picture in picture.
Expand All @@ -49,7 +51,7 @@ import kotlinx.coroutines.launch
*/
class SimplePlayerActivity : ComponentActivity(), ServiceConnection {

private val playerViewModel: SimplePlayerViewModel by viewModels()
private lateinit var playerViewModel: SimplePlayerViewModel
private var layoutStyle: Int = LAYOUT_PLAYLIST

private fun readIntent(intent: Intent) {
Expand All @@ -66,6 +68,8 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val ilHost = (intent.extras?.getSerializable(ARG_IL_HOST) as URL?) ?: IlHost.DEFAULT
playerViewModel = ViewModelProvider(this, factory = SimplePlayerViewModel.Factory(application, ilHost))[SimplePlayerViewModel::class.java]
readIntent(intent)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
Expand Down Expand Up @@ -185,25 +189,27 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection {
companion object {
private const val ARG_PLAYLIST = "ARG_PLAYLIST"
private const val ARG_LAYOUT = "ARG_LAYOUT"
private const val ARG_IL_HOST = "ARG_IL_HOST"
private const val LAYOUT_SIMPLE = 1
private const val LAYOUT_PLAYLIST = 0

/**
* Start activity [SimplePlayerActivity] with [playlist]
*/
fun startActivity(context: Context, playlist: Playlist) {
fun startActivity(context: Context, playlist: Playlist, ilHost: URL = IlHost.DEFAULT) {
val layoutStyle: Int = if (playlist.items.isEmpty() || playlist.items.size > 1) LAYOUT_PLAYLIST else LAYOUT_SIMPLE
val intent = Intent(context, SimplePlayerActivity::class.java)
intent.putExtra(ARG_PLAYLIST, playlist)
intent.putExtra(ARG_LAYOUT, layoutStyle)
intent.putExtra(ARG_IL_HOST, ilHost)
context.startActivity(intent)
}

/**
* Start activity [SimplePlayerActivity] with [demoItem]
* Start activity [SimplePlayerActivity] with DemoItem.
*/
fun startActivity(context: Context, item: DemoItem) {
startActivity(context, Playlist("UniqueItem", listOf(item)))
fun startActivity(context: Context, item: DemoItem, ilHost: URL = IlHost.DEFAULT) {
startActivity(context, Playlist("UniqueItem", listOf(item)), ilHost)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import android.app.Application
import android.util.Log
import android.util.Rational
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.media3.common.C
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException
Expand All @@ -20,15 +22,16 @@ import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
import ch.srgssr.pillarbox.player.extension.toRational
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.net.URL

/**
* Simple player view model than handle a PillarboxPlayer [player]
*/
class SimplePlayerViewModel(application: Application) : AndroidViewModel(application), Player.Listener {
class SimplePlayerViewModel(application: Application, ilHost: URL) : AndroidViewModel(application), Player.Listener {
/**
* Player as PillarboxPlayer
*/
val player = PlayerModule.provideDefaultPlayer(application)
val player = PlayerModule.provideDefaultPlayer(application, ilHost)

private val _pauseOnBackground = MutableStateFlow(true)
private val _displayNotification = MutableStateFlow(false)
Expand Down Expand Up @@ -176,4 +179,13 @@ class SimplePlayerViewModel(application: Application) : AndroidViewModel(applica
companion object {
private const val TAG = "PillarboxDemo"
}

/**
* Factory to create [SimplePlayerViewModel].
*/
class Factory(private val application: Application, private val ilHost: URL) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SimplePlayerViewModel(application, ilHost) as T
}
}
}
4 changes: 4 additions & 0 deletions pillarbox-demo/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@
<string name="clear">Clear</string>
<string name="licence_url">License URL</string>
<string name="play">Play</string>
<string name="server">Server</string>
<string name="production">Production</string>
<string name="stage">Stage</string>
<string name="test">Test</string>
</resources>

0 comments on commit c54e769

Please sign in to comment.