Skip to content
Open
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ Navigation methods for `@Top` destinations are generated in the `CompoassControl
`@Top` annotations are only supported in the main graph, they are disallowed in a sub graph.
**Important**: Don't forget to pass it to the `Compass` again, to make sure the same instance is used by you and the `Compass`

### @Dialog

`@Dialog` marks a destination to be dialog composable in the NavGraph.
Annotating a destination that way leads to the usage of the `dialog` function instead of the `composable` function under the hood.
The destination won't be displayed fullscreen anymore.

### @SubGraph

In addition to a main graph which will generate the `Compass`, destinations can also be grouped together into a subgraph. The subgraph can be defined independently and later included into an
Expand Down
2 changes: 2 additions & 0 deletions build-common/src/main/resources/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ complexity:
LongMethod:
excludes:
- "**/test/**"
LongParameterList:
functionThreshold: 7

naming:
FunctionNaming:
Expand Down
5 changes: 5 additions & 0 deletions compass/api/src/main/kotlin/de/onecode/compass/api/Dialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.onecode.compass.api

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Dialog
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ data class DestinationDescription(
val navigationTargets: List<NavigationTarget>,
val isHome: Boolean,
val isTop: Boolean,
val isDialog: Boolean,
) : NamedWithParameters
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.squareup.kotlinpoet.ksp.toClassName
import de.onecode.compass.api.Destination
import de.onecode.compass.api.Dialog
import de.onecode.compass.api.Home
import de.onecode.compass.api.Navigation
import de.onecode.compass.api.Parameter
Expand Down Expand Up @@ -32,8 +33,17 @@ class DestinationVisitor : KSVisitorVoid() {
?: error("Could not find annotation ${Destination::name} on ${classDeclaration.className}")
val isHome = classDeclaration.filterAnnotations(Home::class).iterator().hasNext()
val isTop = classDeclaration.filterAnnotations(Top::class).iterator().hasNext()
val isDialog = classDeclaration.filterAnnotations(Dialog::class).iterator().hasNext()
val destinationName = destination.getDestinationName(classDeclaration)

if (isDialog && isTop) {
error("Destination $destinationName is marked as ${Dialog::class.qualifiedName} and ${Top::class.qualifiedName}. A Dialog can't also be Top")
}

if (isDialog && isHome) {
error("Destination $destinationName is marked as ${Dialog::class.qualifiedName} and ${Home::class.qualifiedName}. A Dialog can't also be Home")
}

val navTargets = classDeclaration.filterAnnotations(Navigation::class)
.map { navigationAnnotation ->
val target = navigationAnnotation.getParameterValue<KSType>(Navigation::to.name, classDeclaration)
Expand Down Expand Up @@ -76,7 +86,8 @@ class DestinationVisitor : KSVisitorVoid() {
parameters = parameters.toList(),
navigationTargets = navTargets.toList(),
isHome = isHome,
isTop = isTop
isTop = isTop,
isDialog = isDialog,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ fun generateNavigatorCode(graph: GraphDescription): FileSpec {
val destinations = graph.destinations
val subGraphs = graph.subGraphs

val subGraphHasDialog = subGraphs.isNotEmpty() && subGraphs.fold(true) { acc, destination -> acc && destination.destinations.any { it.isDialog } }
val hasDialog = graph.destinations.any { it.isDialog } || subGraphHasDialog

return createFileSpec(graph)
.addImport("androidx.compose.runtime", "CompositionLocalProvider", "compositionLocalOf")
.apply {
if (hasDialog) {
addImport("androidx.navigation.compose", "dialog")
}

if (destinations.isNotEmpty()) {
addType(createCompassController(destinations))
addFunction(createRememberCompassController())
Expand Down Expand Up @@ -108,7 +115,7 @@ private fun FileSpec.Builder.addParameterExtensionsOnSavedStateHandle(destinatio
allParameters.forEach { parameter ->
val existingParameterType = existingParameters[parameter.name]
when {
existingParameterType == null -> {
existingParameterType == null -> {
addFunction(createParameterExtensionOnSavedStateHandle(parameter))
existingParameters[parameter.name] = parameter.type
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private fun DestinationDescription.toNavigationComposableCodeBlock(): CodeBlock

private fun buildComposableCodeBlock(destination: DestinationDescription, statements: CodeBlock.Builder.() -> Unit): CodeBlock =
buildCodeBlock {
beginControlFlow("composable(route = %S, arguments = %L)", destination.route, navigationArgumentsCodeBlock(destination))
beginControlFlow("%L(route = %S, arguments = %L)", destination.toGraphFunctionName(), destination.route, navigationArgumentsCodeBlock(destination))
statements()
endControlFlow()
}
Expand Down Expand Up @@ -80,3 +80,9 @@ private fun navigationArgumentsCodeBlock(description: DestinationDescription): C
}
}
}

private fun DestinationDescription.toGraphFunctionName(): String =
when(isDialog) {
true -> "dialog"
false -> "composable"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ class CheckGraphStateTest {
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
isTop = false,
isDialog = false,
)
val description2 = DestinationDescription(
name = "bar",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
isTop = false,
isDialog = false,
)
val graph = GraphDescription(listOf(description1, description2), emptyList())

Expand All @@ -38,14 +40,16 @@ class CheckGraphStateTest {
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
isTop = false,
isDialog = false,
)
val description2 = DestinationDescription(
name = "bar",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
isTop = false,
isDialog = false,
)
val graph = GraphDescription(listOf(description1, description2), emptyList())

Expand All @@ -62,14 +66,16 @@ class CheckGraphStateTest {
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
isTop = false,
isDialog = false,
)
val description2 = DestinationDescription(
name = "bar",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
isTop = false,
isDialog = false,
)
val graph = GraphDescription(listOf(description1, description2), emptyList())

Expand All @@ -96,14 +102,16 @@ class CheckGraphStateTest {
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
isTop = false,
isDialog = false,
)
val sub2 = DestinationDescription(
name = "sub2",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
isTop = false,
isDialog = false,
)
val sub = SubGraphDescription("sub", listOf(sub1, sub2))
val graph = GraphDescription(emptyList(), listOf(sub))
Expand All @@ -120,14 +128,16 @@ class CheckGraphStateTest {
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
isTop = false,
isDialog = false,
)
val sub2 = DestinationDescription(
name = "sub2",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
isTop = false,
isDialog = false,
)
val sub = SubGraphDescription("sub", listOf(sub1, sub2))
val graph = GraphDescription(emptyList(), listOf(sub))
Expand All @@ -144,14 +154,16 @@ class CheckGraphStateTest {
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
isTop = false,
isDialog = false,
)
val sub2 = DestinationDescription(
name = "sub2",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = true
isTop = true,
isDialog = false,
)
val sub = SubGraphDescription("sub", listOf(sub1, sub2))
val graph = GraphDescription(emptyList(), listOf(sub))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.onecode.compass.ksp.discovery

import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class DestinationVisitorTest {
@Test
Expand Down Expand Up @@ -181,4 +182,14 @@ class DestinationVisitorTest {
assertThat(destinationDescription3.isHome).isFalse()
assertThat(destinationDescription3.isTop).isTrue()
}

@Test
fun `Dialog can't be also Home`() {
val destinationVisitor = DestinationVisitor()
val destination1 = declareDestination(destinationVisitor, name = "Destination1", isHome = true, isDialog = true)

assertThrows<IllegalStateException> {
destination1.accept(destinationVisitor, Unit)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package de.onecode.compass.ksp.discovery

import com.google.common.truth.Truth.assertThat
import de.onecode.compass.ksp.descriptions.DestinationDescription
import de.onecode.compass.ksp.util.destination
import org.junit.jupiter.api.Test

class GraphVisitorTest {
@Test
fun `create graph description without sub graph`() {
val destinations = listOf(
DestinationDescription(name = "D1", parameters = emptyList(), navigationTargets = emptyList(), isHome = true, isTop = false),
DestinationDescription(name = "D2", parameters = emptyList(), navigationTargets = emptyList(), isHome = false, isTop = false)
destination(name = "D1", isHome = true),
destination(name = "D2")
)
val graphVisitor = GraphVisitor(destinations)

Expand All @@ -23,11 +23,11 @@ class GraphVisitorTest {
@Test
fun `create graph description with sub graph`() {

val s1 = DestinationDescription(name = "S1", parameters = emptyList(), navigationTargets = emptyList(), isHome = true, isTop = false)
val s2 = DestinationDescription(name = "S2", parameters = emptyList(), navigationTargets = emptyList(), isHome = false, isTop = false)
val s1 = destination(name = "S1", isHome = true)
val s2 = destination(name = "S2")
val destinations = listOf(
DestinationDescription(name = "D1", parameters = emptyList(), navigationTargets = emptyList(), isHome = true, isTop = false),
DestinationDescription(name = "D2", parameters = emptyList(), navigationTargets = emptyList(), isHome = false, isTop = false),
destination(name = "D1", isHome = true),
destination(name = "D2"),
s1,
s2
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.google.devtools.ksp.symbol.KSName
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSValueArgument
import de.onecode.compass.api.Destination
import de.onecode.compass.api.Dialog
import de.onecode.compass.api.Home
import de.onecode.compass.api.Navigation
import de.onecode.compass.api.Parameter
Expand All @@ -22,11 +23,19 @@ internal fun declareDestination(
name: String = "",
isHome: Boolean = false,
isTop: Boolean = false,
isDialog: Boolean = false,
typeName: String = "TestDestination",
parameters: () -> List<KSAnnotation> = { emptyList() },
navigationTargets: () -> List<KSAnnotation> = { emptyList() },
): KSClassDeclaration {
val annotationsOnDestination = createAnnotationsForDestination(name, isHome, isTop, parameters, navigationTargets)
val annotationsOnDestination = createAnnotationsForDestination(
name = name,
isHome = isHome,
isTop = isTop,
isDialog = isDialog,
parameters = parameters,
navigationTargets = navigationTargets
)

return mockk<KSClassDeclaration> {
every { simpleName } returns ksName(typeName)
Expand All @@ -41,6 +50,7 @@ internal fun createAnnotationsForDestination(
name: String = "",
isHome: Boolean = false,
isTop: Boolean = false,
isDialog: Boolean = false,
parameters: () -> List<KSAnnotation> = { emptyList() },
navigationTargets: () -> List<KSAnnotation> = { emptyList() },
): List<KSAnnotation> {
Expand All @@ -52,7 +62,10 @@ internal fun createAnnotationsForDestination(
val top = isTop
.ifTrue { createAnnotation(Top::class) }

return listOfNotNull(destination, home, top, *parameters().toTypedArray(), *navigationTargets().toTypedArray())
val dialog = isDialog
.ifTrue { createAnnotation(Dialog::class) }

return listOfNotNull(destination, home, top, dialog, *parameters().toTypedArray(), *navigationTargets().toTypedArray())
}

internal fun createDestination(destinationName: String? = null): KSAnnotation = createAnnotation(Destination::class) {
Expand Down
Loading