1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-07-14 23:22:42 +00:00

shared: Inject navigation using Koin as well

Largely inspired from https://insert-koin.io/docs/reference/koin-compose/navigation3/
However I adapted certain things as needed.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2026-06-08 15:44:05 +08:00
parent 6208f9a80c
commit fdb28cdc4a
11 changed files with 135 additions and 62 deletions
@@ -26,7 +26,7 @@ import androidx.fragment.app.FragmentTransaction;
import com.jakewharton.processphoenix.ProcessPhoenix;
import net.newpipe.app.extensions.ContextKt;
import net.newpipe.app.navigation.Screen;
import net.newpipe.app.navigation.Destination;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.NewPipeDatabase;
@@ -684,7 +684,7 @@ public final class NavigationHelper {
}
public static void openAbout(final Context context) {
ContextKt.navigateTo(context, Screen.About.INSTANCE);
ContextKt.navigateTo(context, Destination.About.INSTANCE);
}
public static void openSettings(final Context context) {
+1
View File
@@ -143,6 +143,7 @@ jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:naviga
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
junit = { module = "junit:junit", version.ref = "junit" }
koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin" }
koin-compose-navigation3 = { module = "io.insert-koin:koin-compose-navigation3", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
kotlin-test-core = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines-rx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3", version.ref = "kotlinx-coroutines-rx3" }
+1
View File
@@ -106,6 +106,7 @@ kotlin {
implementation(libs.jetbrains.lifecycle.navigation3)
implementation(libs.kotlinx.serialization.json)
implementation(libs.koin.compose.navigation3)
implementation(libs.koin.compose.viewmodel)
implementation(libs.koin.annotations)
@@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import kotlinx.serialization.json.Json
import net.newpipe.Constants
import net.newpipe.app.navigation.Screen
import net.newpipe.app.navigation.Destination
import net.newpipe.app.theme.currentService
/**
@@ -28,7 +28,7 @@ class ComposeActivity : ComponentActivity() {
setContent {
App(
// TODO: Change when everything is in compose and this is the primary activity
startDestination = Json.decodeFromString<Screen>(
startDestination = Json.decodeFromString<Destination>(
intent.getStringExtra(Constants.INTENT_SCREEN_KEY)!!
),
onCloseRequest = ::finish
@@ -10,12 +10,12 @@ import android.content.Intent
import kotlinx.serialization.json.Json
import net.newpipe.Constants
import net.newpipe.app.ComposeActivity
import net.newpipe.app.navigation.Screen
import net.newpipe.app.navigation.Destination
/**
* Navigates to a given compose destination
*/
fun Context.navigateTo(screen: Screen) = Intent(this, ComposeActivity::class.java).also { intent ->
intent.putExtra(Constants.INTENT_SCREEN_KEY, Json.encodeToString(screen))
fun Context.navigateTo(destination: Destination) = Intent(this, ComposeActivity::class.java).also { intent ->
intent.putExtra(Constants.INTENT_SCREEN_KEY, Json.encodeToString(destination))
startActivity(intent)
}
@@ -7,8 +7,9 @@ package net.newpipe.app
import androidx.compose.runtime.Composable
import net.newpipe.app.di.KoinApp
import net.newpipe.app.navigation.Destination
import net.newpipe.app.navigation.NavDisplay
import net.newpipe.app.navigation.Screen
import net.newpipe.app.navigation.navModule
import net.newpipe.app.theme.AppTheme
import org.koin.compose.KoinApplication
import org.koin.plugin.module.dsl.koinConfiguration
@@ -21,11 +22,17 @@ import org.koin.plugin.module.dsl.koinConfiguration
*/
@Composable
fun App(
startDestination: Screen = Screen.About,
startDestination: Destination = Destination.About,
onCloseRequest: () -> Unit,
withKoin: @Composable () -> Unit = {}
) {
KoinApplication(configuration = koinConfiguration<KoinApp>()) {
KoinApplication(
configuration = koinConfiguration<KoinApp>(
appDeclaration = {
modules(navModule())
}
)
) {
AppTheme {
NavDisplay(
startDestination = startDestination,
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.navigation
import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable
import net.newpipe.app.model.License
/**
* Destinations for navigation in compose
*/
@Serializable
sealed interface Destination : NavKey {
@Serializable
data object About : Destination
}
@@ -6,30 +6,42 @@
package net.newpipe.app.navigation
import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.compose.runtime.saveable.rememberSerializable
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import net.newpipe.app.screen.about.AboutScreen
import androidx.savedstate.compose.serialization.serializers.SnapshotStateListSerializer
import org.koin.compose.koinInject
import org.koin.compose.navigation3.koinEntryProvider
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.parameter.parametersOf
/**
* Navigation display for compose screens
* @param startDestination Starting destination for the app
* @param onCloseRequest Callback to close app when there are no more screens left in backstack
* @param navigator Navigator to help with navigation, injected by Koin
* @param onCloseRequest Callback to close the app
*/
@OptIn(KoinExperimentalAPI::class)
@Composable
fun NavDisplay(startDestination: Screen, onCloseRequest: () -> Unit) {
val backstack = rememberNavBackStack(screenConfig, startDestination)
fun onNavigateUp() = if (backstack.size > 1) backstack.removeLastOrNull() else onCloseRequest()
fun NavDisplay(
startDestination: Destination,
onCloseRequest: () -> Unit,
navigator: Navigator = koinInject {
parametersOf(startDestination, onCloseRequest)
}
) {
val backstack = rememberSerializable(serializer = SnapshotStateListSerializer<Destination>()) {
navigator.backstack
}
NavDisplay(
backStack = backstack,
entryProvider = entryProvider {
entry<Screen.About> {
AboutScreen(
onNavigateUp = ::onNavigateUp
)
}
}
onBack = { navigator.navigateUp() },
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator()
),
entryProvider = koinEntryProvider()
)
}
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.navigation
import androidx.compose.runtime.mutableStateListOf
import co.touchlab.kermit.Logger
import net.newpipe.app.screen.about.AboutScreen
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.annotation.Provided
import org.koin.core.annotation.Singleton
import org.koin.dsl.module
import org.koin.dsl.navigation3.navigation
import org.koin.plugin.module.dsl.single
/**
* Navigation module to make navigation easier with nav3
*
* There is currently no annotation to handle this so we are using DSL API of Koin
*/
@OptIn(KoinExperimentalAPI::class)
fun navModule() = module {
single<Navigator>()
navigation<Destination.About> {
AboutScreen()
}
}
/**
* Helper to navigate up and to different destinations in compose
*/
@Singleton
class Navigator(
@Provided
private val startDestination: Destination,
@Provided
private val onCloseRequest: () -> Unit
) {
/**
* Navigation backstack in compose
*/
val backstack = mutableStateListOf(startDestination)
/**
* Navigates to the given destination
*/
fun navigateTo(destination: Destination) = backstack.add(destination)
/**
* Navigates to the previous entry in the backstack
*/
fun navigateUp() = when {
backstack.size > 1 -> backstack.removeLastOrNull()
else -> {
Logger.i(messageString = "Cannot remove the only entry in backstack!")
onCloseRequest()
}
}
}
@@ -1,35 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.navigation
import androidx.navigation3.runtime.NavKey
import androidx.savedstate.serialization.SavedStateConfiguration
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
/**
* Destinations for navigation in compose
*/
@Serializable
sealed interface Screen : NavKey {
@Serializable
data object About : Screen
}
/**
* Saved state configuration for screens
*/
@OptIn(ExperimentalSerializationApi::class)
internal val screenConfig = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclassesOfSealed<Screen>()
}
}
}
@@ -25,17 +25,19 @@ import androidx.compose.ui.tooling.preview.PreviewWrapper
import androidx.compose.ui.util.fastForEachIndexed
import kotlinx.coroutines.launch
import net.newpipe.app.composable.TopAppBar
import net.newpipe.app.navigation.Navigator
import net.newpipe.app.preview.ThemePreviewProvider
import net.newpipe.app.screen.about.navigation.Page
import net.newpipe.app.theme.currentServiceScheme
import newpipe.shared.generated.resources.Res
import newpipe.shared.generated.resources.title_activity_about
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.koinInject
@Composable
fun AboutScreen(onNavigateUp: () -> Unit) {
fun AboutScreen(navigator: Navigator = koinInject()) {
AboutScreenContent(
onNavigateUp = onNavigateUp
onNavigateUp = { navigator.navigateUp() }
)
}