Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ dependencies {

// UI
implementation(libs.bundles.androidx.compose)
implementation(libs.androidx.activity)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.palette)
implementation(libs.androidx.savedstate)
implementation(libs.androidx.tv.material)
implementation(libs.coil.compose)
debugImplementation(libs.androidx.compose.ui.tooling)
Expand Down
23 changes: 14 additions & 9 deletions app/src/main/kotlin/nl/ndat/tvlauncher/data/Destinations.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
package nl.ndat.tvlauncher.data

import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.compose.runtime.Composable
import kotlinx.serialization.Serializable
import nl.ndat.tvlauncher.ui.tab.apps.AppsTab
import nl.ndat.tvlauncher.ui.tab.home.HomeTab

interface Destination {
@Composable
fun Content()
}

object Destinations {
@Serializable
object Home
object Home : Destination {
@Composable
override fun Content() = HomeTab()
}

@Serializable
object Apps
object Apps : Destination {
@Composable
override fun Content() = AppsTab()
}
}

val DefaultDestination = Destinations.Home

fun NavGraphBuilder.createDestinationsGraph() {
composable<Destinations.Home> { HomeTab() }
composable<Destinations.Apps> { AppsTab() }
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package nl.ndat.tvlauncher.ui.screen.launcher

import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.ui.NavDisplay
import nl.ndat.tvlauncher.ui.toolbar.Toolbar
import nl.ndat.tvlauncher.util.composition.LocalNavController
import nl.ndat.tvlauncher.util.composition.LocalNavGraph
import nl.ndat.tvlauncher.util.composition.LocalBackStack
import nl.ndat.tvlauncher.util.composition.ProvideNavigation
import nl.ndat.tvlauncher.util.modifier.autoFocus

@Composable
fun LauncherScreen() {
ProvideNavigation() {
ProvideNavigation {
Column(
modifier = Modifier
.fillMaxSize()
Expand All @@ -35,11 +33,14 @@ fun LauncherScreen() {
modifier = Modifier
.autoFocus()
) {
NavHost(
navController = LocalNavController.current,
graph = LocalNavGraph.current,
enterTransition = { fadeIn() },
exitTransition = { fadeOut() }
val backStack = LocalBackStack.current
NavDisplay(
backStack = backStack,
entryProvider = { route ->
NavEntry(route) {
route.Content()
}
},
)
}
}
Expand Down
30 changes: 9 additions & 21 deletions app/src/main/kotlin/nl/ndat/tvlauncher/ui/toolbar/ToolbarTabs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,44 @@ package nl.ndat.tvlauncher.ui.toolbar

import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.get
import androidx.tv.material3.Tab
import androidx.tv.material3.TabRow
import androidx.tv.material3.Text
import kotlinx.coroutines.flow.map
import nl.ndat.tvlauncher.R
import nl.ndat.tvlauncher.data.Destinations
import nl.ndat.tvlauncher.util.composition.LocalNavController
import nl.ndat.tvlauncher.util.composition.LocalNavGraph
import nl.ndat.tvlauncher.util.composition.LocalBackStack

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ToolbarTabs(
modifier: Modifier,
) {
val navController = LocalNavController.current
val navGraph = LocalNavGraph.current
val currentDestinationId by navController.currentBackStackEntryFlow.map { it.destination.id }.collectAsState(null)
val backStack = LocalBackStack.current
val currentDestination = backStack.lastOrNull()
val tabs = mapOf(
Destinations.Home to stringResource(R.string.tab_home),
Destinations.Apps to stringResource(R.string.tab_apps),
)

TabRow(
selectedTabIndex = tabs.keys.indexOfFirst { destination -> navGraph[destination].id == currentDestinationId },
selectedTabIndex = tabs.keys.indexOfFirst { destination -> destination == currentDestination },
modifier = modifier.focusRestorer(),
) {
tabs.toList().forEachIndexed { index, (destination, name) ->
key(index) {
val selected = navGraph[destination].id == currentDestinationId
Tab(
selected = selected,
selected = destination == currentDestination,
onFocus = {
if (!selected) {
navController.navigate(destination) {
if (currentDestinationId != null) {
popUpTo(currentDestinationId!!) {
inclusive = true
}
}
}
}
if (destination == currentDestination) return@Tab
if (currentDestination != Destinations.Home) backStack.removeLastOrNull()

backStack.add(destination)
},
modifier = Modifier.padding(16.dp, 8.dp)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,21 @@ package nl.ndat.tvlauncher.util.composition

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.navigation.NavGraph
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import androidx.navigation.createGraph
import nl.ndat.tvlauncher.data.DefaultDestination
import nl.ndat.tvlauncher.data.createDestinationsGraph
import nl.ndat.tvlauncher.data.Destination

val LocalNavController = staticCompositionLocalOf<NavHostController> {
error("NavController not provided")
val LocalBackStack = staticCompositionLocalOf<SnapshotStateList<Destination>> {
error("LocalBackStack not provided")
}

val LocalNavGraph = staticCompositionLocalOf<NavGraph> {
error("NavGraph not provided")
}

@Composable
fun ProvideNavigation(
navController: NavHostController = rememberNavController(),
navGraph: NavGraph = remember(navController) {
navController.createGraph(DefaultDestination) {
createDestinationsGraph()
}
},
backStack: SnapshotStateList<Destination> = remember { mutableStateListOf<Destination>(DefaultDestination) },
content: @Composable () -> Unit,
) = CompositionLocalProvider(
LocalNavController provides navController,
LocalNavGraph provides navGraph,
LocalBackStack provides backStack,
content = content,
)
20 changes: 13 additions & 7 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ android-compileSdk = "36"
android-minSdk = "21"
android-plugin = "8.10.1"
android-targetSdk = "36"
androidx-activity = "1.10.1"
androidx-activity = "1.12.0-alpha03"
androidx-appcompat = "1.7.1"
androidx-compose-foundation = "1.8.3"
androidx-compose-ui = "1.8.3"
androidx-compose-foundation = "1.9.0-beta01"
androidx-compose-runtime = "1.9.0-beta01"
androidx-compose-ui = "1.9.0-beta01"
androidx-core = "1.16.0"
androidx-core-role = "1.1.0"
androidx-navigation = "2.9.0"
androidx-navigation3 = "1.0.0-alpha04"
androidx-palette = "1.0.0"
androidx-savedstate = "1.3.0"
androidx-tv = "1.0.0"
androidx-tvprovider = "1.1.0"
coil = "2.7.0"
Expand All @@ -30,18 +32,20 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }

[libraries]
androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-activity" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidx-compose-foundation" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "androidx-compose-runtime" }
androidx-compose-runtime-savable = { module = "androidx.compose.runtime:runtime-saveable", version.ref = "androidx-compose-runtime" }
androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidx-compose-ui" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidx-compose-ui" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "androidx-compose-ui" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-core-role = { module = "androidx.core:core-role", version.ref = "androidx-core-role" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "androidx-navigation" }
androidx-navigation-ui = { module = "androidx.navigation:navigation-ui", version.ref = "androidx-navigation" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "androidx-navigation3" }
androidx-palette = { module = "androidx.palette:palette-ktx", version.ref = "androidx-palette" }
androidx-savedstate = { module = "androidx.savedstate:savedstate", version.ref = "androidx-savedstate" }
androidx-tv-material = { module = "androidx.tv:tv-material", version.ref = "androidx-tv" }
androidx-tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "androidx-tvprovider" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
Expand All @@ -55,6 +59,8 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
[bundles]
androidx-compose = [
"androidx-compose-foundation",
"androidx-compose-runtime",
"androidx-compose-runtime-savable",
"androidx-compose-ui",
"androidx-compose-ui-tooling-preview",
]
Expand Down