Skip to content

Commit dc02c99

Browse files
committed
Add top navigation back in
1 parent 5f4b614 commit dc02c99

File tree

7 files changed

+153
-67
lines changed

7 files changed

+153
-67
lines changed

app/src/main/java/de/christinecoenen/code/zapp/tv2/about/AboutScreen.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ import androidx.tv.material3.Icon
1717
import androidx.tv.material3.MaterialTheme
1818
import androidx.tv.material3.Text
1919
import de.christinecoenen.code.zapp.R
20+
import de.christinecoenen.code.zapp.tv2.main.navigation.MainScreenLocation
2021
import de.christinecoenen.code.zapp.tv2.theme.AppTheme
2122
import de.christinecoenen.code.zapp.tv2.theme.TvScreenPreview
23+
import kotlinx.serialization.Serializable
24+
25+
@Serializable
26+
data object AboutScreenLocation : MainScreenLocation(
27+
titleResId = R.string.menu_about_short,
28+
)
2229

2330
@TvScreenPreview
2431
@Composable

app/src/main/java/de/christinecoenen/code/zapp/tv2/live/LiveScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ import androidx.compose.ui.Alignment
1515
import androidx.compose.ui.Modifier
1616
import androidx.compose.ui.unit.dp
1717
import androidx.lifecycle.compose.collectAsStateWithLifecycle
18+
import de.christinecoenen.code.zapp.R
1819
import de.christinecoenen.code.zapp.app.livestream.ui.ProgramInfoViewModel
1920
import de.christinecoenen.code.zapp.models.channels.ChannelModel
21+
import de.christinecoenen.code.zapp.tv2.main.navigation.MainScreenLocation
22+
import kotlinx.serialization.Serializable
2023
import org.koin.androidx.compose.koinViewModel
2124

25+
@Serializable
26+
data object LiveScreenLocation : MainScreenLocation(
27+
titleResId = R.string.activity_main_tab_live,
28+
)
29+
2230
@Composable
2331
fun LiveScreen(
2432
liveScreenViewModel: LiveScreenViewModel = koinViewModel(),

app/src/main/java/de/christinecoenen/code/zapp/tv2/main/MainActivity.kt

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,40 @@ import androidx.activity.compose.setContent
66
import androidx.compose.foundation.background
77
import androidx.compose.foundation.layout.Box
88
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.mutableStateOf
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.runtime.setValue
13+
import androidx.compose.ui.Alignment
914
import androidx.compose.ui.Modifier
1015
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
11-
import androidx.navigation3.runtime.NavKey
16+
import androidx.navigation3.runtime.NavEntry
1217
import androidx.navigation3.runtime.entryProvider
1318
import androidx.navigation3.runtime.rememberNavBackStack
1419
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
1520
import androidx.navigation3.ui.NavDisplay
1621
import androidx.tv.material3.MaterialTheme
1722
import de.christinecoenen.code.zapp.app.player.VideoInfo
23+
import de.christinecoenen.code.zapp.tv2.about.AboutScreen
24+
import de.christinecoenen.code.zapp.tv2.about.AboutScreenLocation
1825
import de.christinecoenen.code.zapp.tv2.live.LiveScreen
26+
import de.christinecoenen.code.zapp.tv2.live.LiveScreenLocation
27+
import de.christinecoenen.code.zapp.tv2.main.navigation.MainScreenLocation
28+
import de.christinecoenen.code.zapp.tv2.mediathek.MediaCenterLocation
29+
import de.christinecoenen.code.zapp.tv2.mediathek.MediaCenterScreen
1930
import de.christinecoenen.code.zapp.tv2.player.PlayerScreen
31+
import de.christinecoenen.code.zapp.tv2.player.PlayerScreenLocation
2032
import de.christinecoenen.code.zapp.tv2.theme.AppTheme
21-
import kotlinx.serialization.Serializable
2233

2334
class MainActivity : ComponentActivity() {
2435

25-
// TODO: move to components and maybe rename those NavKeys
26-
@Serializable
27-
private data object LiveScreen : NavKey
28-
29-
@Serializable
30-
private data class Player(val videoInfo: VideoInfo) : NavKey
31-
3236
override fun onCreate(savedInstanceState: Bundle?) {
3337
super.onCreate(savedInstanceState)
3438

3539
setContent {
36-
val backStack = rememberNavBackStack(LiveScreen)
40+
val backStack = rememberNavBackStack(LiveScreenLocation)
41+
val mainLocations = listOf(LiveScreenLocation, MediaCenterLocation, AboutScreenLocation)
42+
var selectedMainLocation by remember { mutableStateOf(mainLocations.first()) }
3743

3844
AppTheme {
3945
Box(
@@ -42,36 +48,71 @@ class MainActivity : ComponentActivity() {
4248
.background(MaterialTheme.colorScheme.surface)
4349
) {
4450
NavDisplay(
51+
modifier = Modifier.fillMaxSize(),
4552
entryDecorators = listOf(
4653
rememberSaveableStateHolderNavEntryDecorator(),
4754
rememberViewModelStoreNavEntryDecorator()
4855
),
4956
backStack = backStack,
50-
modifier = Modifier.fillMaxSize(),
5157
entryProvider = entryProvider {
52-
entry<LiveScreen> {
58+
entry<LiveScreenLocation> {
5359
LiveScreen(
5460
onChannelClick = { channel ->
55-
backStack.add(Player(VideoInfo.fromChannel(channel)))
61+
backStack.add(
62+
PlayerScreenLocation(VideoInfo.fromChannel(channel))
63+
)
64+
}
65+
)
66+
}
67+
68+
entry<MediaCenterLocation> {
69+
MediaCenterScreen(
70+
onShowSelected = { show ->
71+
backStack.add(
72+
PlayerScreenLocation(
73+
// TODO: extend VideoInfo::fromShow
74+
VideoInfo(title = show.title, url = show.videoUrl)
75+
)
76+
)
5677
}
5778
)
5879
}
5980

60-
entry<Player> { key ->
81+
entry<AboutScreenLocation> {
82+
AboutScreen()
83+
}
84+
85+
entry<PlayerScreenLocation> { key ->
6186
PlayerScreen(videoInfo = key.videoInfo)
6287
}
6388

6489
// TODO: add remaining screens
6590
}
6691
)
6792

68-
// TODO: add tab top navigation back in
69-
/*TopNavigation(
70-
selectedTabIndex = currentSelectedTabIndex,
71-
onTabSelected = { index -> navigationViewModel.selectMainTab(index) },
72-
tabStringIds = navigationViewModel.mainTabTitleResIds,
73-
modifier = Modifier.align(Alignment.TopCenter)
74-
)*/
93+
// Show/hide main navigation based on backstack
94+
NavDisplay(
95+
modifier = Modifier.align(Alignment.TopCenter),
96+
backStack = backStack,
97+
entryProvider = { key ->
98+
when (key) {
99+
is MainScreenLocation -> NavEntry(key) {
100+
TopNavigation(
101+
locations = mainLocations,
102+
selectedLocation = selectedMainLocation,
103+
onLocationSelected = { location ->
104+
selectedMainLocation = location
105+
backStack.clear()
106+
backStack.add(location)
107+
},
108+
modifier = Modifier.align(Alignment.TopCenter)
109+
)
110+
}
111+
112+
else -> NavEntry(key) {}
113+
}
114+
}
115+
)
75116
}
76117
}
77118
}

app/src/main/java/de/christinecoenen/code/zapp/tv2/main/TopNavigation.kt

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,61 +19,67 @@ import androidx.compose.ui.unit.dp
1919
import androidx.tv.material3.Tab
2020
import androidx.tv.material3.TabRow
2121
import androidx.tv.material3.Text
22-
import de.christinecoenen.code.zapp.R
22+
import de.christinecoenen.code.zapp.tv2.about.AboutScreenLocation
23+
import de.christinecoenen.code.zapp.tv2.live.LiveScreenLocation
24+
import de.christinecoenen.code.zapp.tv2.main.navigation.MainScreenLocation
25+
import de.christinecoenen.code.zapp.tv2.mediathek.MediaCenterLocation
2326
import de.christinecoenen.code.zapp.tv2.theme.AppTheme
2427
import de.christinecoenen.code.zapp.tv2.theme.TvPreview
2528

2629
@TvPreview
2730
@Composable
2831
fun TopNavigation(
29-
modifier: Modifier = Modifier,
30-
tabStringIds: List<Int> = listOf(
31-
R.string.app_name,
32-
R.string.activity_main_tab_live
33-
),
34-
selectedTabIndex: Int = 0,
35-
onTabSelected: (index: Int) -> Unit = {}
32+
modifier: Modifier = Modifier,
33+
locations: List<MainScreenLocation> = listOf(
34+
LiveScreenLocation,
35+
MediaCenterLocation,
36+
AboutScreenLocation
37+
),
38+
selectedLocation: MainScreenLocation = locations.first(),
39+
onLocationSelected: (location: MainScreenLocation) -> Unit = {}
3640
) {
37-
AppTheme {
38-
val focusRequester = remember { FocusRequester() }
39-
var hasFocus by remember { mutableStateOf(false) }
41+
AppTheme {
42+
val focusRequester = remember { FocusRequester() }
43+
val selectedTabIndex = locations.indexOf(selectedLocation)
44+
var hasFocus by remember { mutableStateOf(false) }
4045

41-
BackHandler(selectedTabIndex != 0 || !hasFocus) {
42-
onTabSelected(0)
43-
focusRequester.requestFocus()
44-
}
46+
BackHandler(selectedTabIndex != 0 || !hasFocus) {
47+
onLocationSelected(locations.first())
48+
focusRequester.requestFocus()
49+
}
4550

46-
LaunchedEffect(Unit) {
47-
focusRequester.requestFocus()
48-
}
51+
// TODO: this seems to grap focus when navigating back from non main screen
52+
LaunchedEffect(Unit) {
53+
focusRequester.requestFocus()
54+
}
4955

50-
TabRow(
51-
selectedTabIndex = selectedTabIndex,
52-
modifier = modifier
53-
.padding(top = 32.dp, bottom = 16.dp)
54-
.focusGroup()
55-
.focusRestorer()
56-
.onFocusChanged { hasFocus = it.hasFocus }
57-
) {
58-
tabStringIds.forEachIndexed { index, tabResId ->
59-
val isSelected = index == selectedTabIndex
56+
TabRow(
57+
selectedTabIndex = selectedTabIndex,
58+
modifier = modifier
59+
.padding(top = 32.dp, bottom = 16.dp)
60+
.focusGroup()
61+
.focusRestorer()
62+
.onFocusChanged { hasFocus = it.hasFocus }
63+
) {
64+
locations.forEach { location ->
65+
val isSelected = location == selectedLocation
6066

61-
Tab(
62-
selected = isSelected,
63-
onFocus = { onTabSelected(index) },
64-
modifier = Modifier
65-
.then(if (isSelected) Modifier.focusRequester(focusRequester) else Modifier)
66-
) {
67-
Text(
68-
text = stringResource(tabResId),
69-
modifier = Modifier
70-
.padding(
71-
horizontal = 16.dp,
72-
vertical = 10.dp
73-
)
74-
)
75-
}
76-
}
77-
}
78-
}
67+
Tab(
68+
selected = isSelected,
69+
onFocus = { onLocationSelected(location) },
70+
modifier = Modifier
71+
.then(if (isSelected) Modifier.focusRequester(focusRequester) else Modifier)
72+
) {
73+
Text(
74+
text = stringResource(location.titleResId),
75+
modifier = Modifier
76+
.padding(
77+
horizontal = 16.dp,
78+
vertical = 10.dp
79+
)
80+
)
81+
}
82+
}
83+
}
84+
}
7985
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package de.christinecoenen.code.zapp.tv2.main.navigation
2+
3+
import androidx.annotation.StringRes
4+
import androidx.navigation3.runtime.NavKey
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
open class MainScreenLocation(
9+
@StringRes
10+
val titleResId: Int,
11+
) : NavKey

app/src/main/java/de/christinecoenen/code/zapp/tv2/mediathek/MediaCenterScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@ import androidx.compose.runtime.setValue
1616
import androidx.compose.ui.Modifier
1717
import androidx.compose.ui.unit.dp
1818
import androidx.paging.compose.collectAsLazyPagingItems
19+
import de.christinecoenen.code.zapp.R
1920
import de.christinecoenen.code.zapp.models.shows.MediathekShow
21+
import de.christinecoenen.code.zapp.tv2.main.navigation.MainScreenLocation
2022
import de.christinecoenen.code.zapp.tv2.theme.TvScreenPreview
23+
import kotlinx.serialization.Serializable
2124
import org.koin.androidx.compose.koinViewModel
2225

26+
@Serializable
27+
data object MediaCenterLocation : MainScreenLocation(
28+
titleResId = R.string.activity_main_tab_mediathek,
29+
)
30+
2331
@TvScreenPreview
2432
@Composable
2533
fun MediaCenterScreen(

app/src/main/java/de/christinecoenen/code/zapp/tv2/player/PlayerScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@ import androidx.compose.runtime.setValue
1515
import androidx.compose.ui.Alignment
1616
import androidx.compose.ui.Modifier
1717
import androidx.media3.ui.compose.PlayerSurface
18+
import androidx.navigation3.runtime.NavKey
1819
import de.christinecoenen.code.zapp.app.player.Player
1920
import de.christinecoenen.code.zapp.app.player.VideoInfo
21+
import kotlinx.serialization.Serializable
2022
import org.koin.compose.koinInject
2123

24+
@Serializable
25+
data class PlayerScreenLocation(val videoInfo: VideoInfo) : NavKey
26+
2227
@Composable
2328
fun PlayerScreen(
2429
videoInfo: VideoInfo,

0 commit comments

Comments
 (0)