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:
@@ -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) {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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() }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user