├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── build.gradle.kts ├── composable-routes-lib ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── dev │ └── matrix │ └── compose_routes │ └── ComposableRoute.kt ├── composable-routes-processor ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── dev │ └── matrix │ └── compose_routes │ └── compiler │ ├── AnnotationProcessor.kt │ ├── AnnotationProcessorState.kt │ ├── RouteClassGenerator.kt │ ├── model │ ├── RouteClassName.kt │ ├── RouteDestination.kt │ ├── RouteDestinationArg.kt │ └── nav_type │ │ ├── BooleanNavType.kt │ │ ├── EnumNavType.kt │ │ ├── FloatNavType.kt │ │ ├── IntegerNavType.kt │ │ ├── NavControllerNavType.kt │ │ ├── NavType.kt │ │ ├── ParcelableNavType.kt │ │ ├── SerializableNavType.kt │ │ └── StringNavType.kt │ ├── serializers │ ├── ParcelableSerializer.kt │ └── SerializableSerializer.kt │ └── utils │ └── UrlEncoder.kt ├── composable-routes-test ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── dev │ │ └── matrix │ │ └── compose_routes │ │ └── MyScreen.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-night │ └── themes.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | composable-routes -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rostyslav Lesovyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ComposableRoutes 2 | 3 | [![Release](https://jitpack.io/v/MatrixDev/ComposableRoutes.svg)](https://jitpack.io/#MatrixDev/ComposableRoutes) 4 | 5 | This library is a helper for the Android Compose Navigation routes: 6 | - generates routes for the annotated screens 7 | - provides helpers to register routes in the `NavController` 8 | - reduces boiler-plate code for arguments parsing 9 | - and more... 10 | 11 | # How does it work? 12 | 13 | Add annotation to the Compose screen: 14 | 15 | ```kotlin 16 | @Composable 17 | @ComposableRoute 18 | fun HomeScreen() { 19 | // screen without arguments 20 | } 21 | 22 | @Composable 23 | @ComposableRoute 24 | fun ContactScreen(id: String) { 25 | // screen with required parameter 26 | } 27 | 28 | @Composable 29 | @ComposableRoute 30 | fun AboutScreen(overrideTitle: String?) { 31 | // screen with optional arguments 32 | } 33 | 34 | @Composable 35 | @ComposableRoute 36 | fun SettingsScreen(navController: NavController) { 37 | // in case you want to pass navController directly 38 | } 39 | ``` 40 | 41 | Build your project and it will automatically generate `NavRoutes` object. 42 | 43 | The next step is to register this route: 44 | 45 | ```kotlin 46 | val navController = rememberNavController() 47 | NavHost(navController = navController, startDestination = NavRoutes.HomeScreen.PATH) { 48 | 49 | NavRoutes.HomeScreen.register(this) 50 | NavRoutes.ContactScreen.register(this) 51 | NavRoutes.AboutScreen.register(this) 52 | NavRoutes.SettingsScreen.register(this, navController = navController) 53 | 54 | } 55 | ``` 56 | 57 | `NavRoutes` also provides a helper to register all generated routes with one call: 58 | 59 | ```kotlin 60 | val navController = rememberNavController() 61 | NavHost(navController = navController, startDestination = NavRoutes.HomeScreen.PATH) { 62 | 63 | NavRoutes.registerAll(this, navController = navController) 64 | 65 | } 66 | ``` 67 | 68 | There are few ways to navigate to the specified route: 69 | 70 | ```kotlin 71 | // NavController.navigate 72 | navController.navigate(NavRoutes.HomeScreen()) 73 | 74 | // generated navigateToXXX helpers 75 | navController.navigateToHomeScreen() 76 | 77 | // required arguments 78 | navController.navigateToContactScreen(id = "1") 79 | 80 | // optional arguments 81 | navController.navigateToAboutScreen(overrideTitle = "Ny About Title") 82 | navController.navigateToAboutScreen() 83 | ``` 84 | 85 | Library supports following type of arguments: 86 | - primitives (`Boolean`, `Int`, `Float`, etc.) 87 | - `String`s 88 | - `Parcelable` objects 89 | - `Serializable` objects 90 | 91 | # How to pass NavController to the Screen? 92 | 93 | There are few ways to pass `NavController` to the screen: 94 | 95 | ## Solution #1: By passing it directly to the Screen 96 | 97 | All related function will require `NavController` as an argument if at least one Screen takes `NavController` as argument. For example if we have a screen: 98 | 99 | ```kotlin 100 | @Composable 101 | @ComposableRoute 102 | fun ScreenWithNavController(navController: NavController) { 103 | // ... 104 | } 105 | ``` 106 | 107 | Than all related `register` and `registerAll` functions will start to require `NavController` as the argument: 108 | 109 | ```kotlin 110 | NavRoutes.registerAll(this, navController = navController) 111 | // or 112 | NavRoutes.ScreenWithNavController.register(this, navController = navController) 113 | ``` 114 | 115 | ## Solution #2: By using CompositionLocalProvider 116 | 117 | On the other hand `CompositionLocalProvider` can be used if you don't want to pass `NavController` directly. 118 | 119 | The first step is to declare our `CompositionLocal` key for the provider: 120 | ```kotlin 121 | val LocalNavController = compositionLocalOf { 122 | error("NavController was not provided") 123 | } 124 | ``` 125 | 126 | Then we need to add this key to the provider: 127 | ```kotlin 128 | val navController = rememberNavController() 129 | CompositionLocalProvider(LocalNavController provides navController) { 130 | NavHost(navController = navController, startDestination = NavRoutes.JoinAsGuestScreen()) { 131 | NavRoutes.registerAll(this) 132 | } 133 | } 134 | ``` 135 | 136 | That's it. Now you can get `NavController` in any descendant `@Composable` function like this: 137 | ```kotlin 138 | LocalNavController.current.navigateToHomeScreen() 139 | ``` 140 | 141 | # Add to your project 142 | 143 | To add this library into your project: 144 | 145 | Step 1. Add a JitPack repository to your root build.gradle: 146 | 147 | ```kotlin 148 | allprojects { 149 | repositories { 150 | maven(url = "https://jitpack.io") 151 | } 152 | } 153 | ``` 154 | 155 | Step 2. Add library and compiler dependencies: 156 | 157 | ```kotlin 158 | dependencies { 159 | implementation("com.github.MatrixDev.ComposableRoutes:composable-routes-lib:{latest}") 160 | kapt("com.github.MatrixDev.ComposableRoutes:composable-routes-processor:{latest}") 161 | } 162 | ``` 163 | 164 | More info can be found at https://jitpack.io/#MatrixDev/ComposableRoutes 165 | 166 | # TODO 167 | 168 | These are just few more nice things to have in the future: 169 | - Migrate from KAPT to KSP 170 | 171 | # License 172 | 173 | ``` 174 | MIT License 175 | 176 | Copyright (c) 2018 Rostyslav Lesovyi 177 | 178 | Permission is hereby granted, free of charge, to any person obtaining a copy 179 | of this software and associated documentation files (the "Software"), to deal 180 | in the Software without restriction, including without limitation the rights 181 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 182 | copies of the Software, and to permit persons to whom the Software is 183 | furnished to do so, subject to the following conditions: 184 | 185 | The above copyright notice and this permission notice shall be included in all 186 | copies or substantial portions of the Software. 187 | 188 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 189 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 190 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 191 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 192 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 193 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 194 | SOFTWARE. 195 | ``` 196 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | val kotlinVersion by extra { "1.5.31" } 3 | val composeRoutesVersion by extra { "0.1.14" } 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath("com.android.tools.build:gradle:7.0.3") 12 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") 13 | } 14 | } 15 | 16 | tasks.register("clean", Delete::class) { 17 | delete(rootProject.buildDir) 18 | rootProject.childProjects.forEach { project -> 19 | delete(project.value.buildDir) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /composable-routes-lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /composable-routes-lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("kotlin") 4 | id("maven-publish") 5 | } 6 | 7 | java { 8 | sourceCompatibility = JavaVersion.VERSION_1_8 9 | targetCompatibility = JavaVersion.VERSION_1_8 10 | } 11 | 12 | publishing { 13 | val composeRoutesVersion: String by rootProject.extra 14 | publications { 15 | create("maven") { 16 | groupId = "dev.matrix.composable-routes" 17 | artifactId = "composable-routes-lib" 18 | version = composeRoutesVersion 19 | from(components.getByName("java")) 20 | } 21 | } 22 | repositories { 23 | maven { 24 | name = "GitHubPackages" 25 | setUrl("https://maven.pkg.github.com/MatrixDev/ComposableRoutes") 26 | credentials { 27 | username = System.getenv("GITHUB_ACTOR") 28 | password = System.getenv("GITHUB_TOKEN") 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composable-routes-lib/src/main/java/dev/matrix/compose_routes/ComposableRoute.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes 2 | 3 | /** 4 | * @author matrixdev 5 | */ 6 | @Target(AnnotationTarget.FUNCTION) 7 | @Retention(AnnotationRetention.SOURCE) 8 | annotation class ComposableRoute( 9 | val generatedClassName: String = "NavRoutes", 10 | val generatedClassPackage: String = "dev.matrix.compose_routes", 11 | ) 12 | -------------------------------------------------------------------------------- /composable-routes-processor/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /composable-routes-processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("kotlin") 4 | id("kotlin-kapt") 5 | id("maven-publish") 6 | } 7 | 8 | java { 9 | sourceCompatibility = JavaVersion.VERSION_1_8 10 | targetCompatibility = JavaVersion.VERSION_1_8 11 | } 12 | 13 | kotlin { 14 | sourceSets.all { 15 | languageSettings.apply { 16 | optIn("com.squareup.kotlinpoet.DelicateKotlinPoetApi") 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation(project(":composable-routes-lib")) 23 | 24 | // Kotlin runtime 25 | val kotlinVersion: String by rootProject.extra 26 | implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") 27 | 28 | // Kotlin code generation 29 | implementation("com.squareup:kotlinpoet:1.10.2") /*{ 30 | exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8" 31 | }*/ 32 | implementation("com.squareup:kotlinpoet-metadata:1.10.2") 33 | implementation("com.squareup:kotlinpoet-metadata-specs:1.9.0") 34 | 35 | // Auto META-INF processor info generator 36 | //noinspection AnnotationProcessorOnCompilePath 37 | compileOnly("com.google.auto.service:auto-service:1.0-rc7") 38 | 39 | // Incremental processor support 40 | compileOnly("net.ltgt.gradle.incap:incap:0.3") 41 | kapt("net.ltgt.gradle.incap:incap-processor:0.3") 42 | } 43 | 44 | publishing { 45 | val composeRoutesVersion: String by rootProject.extra 46 | publications { 47 | create("maven") { 48 | groupId = "dev.matrix.composable-routes" 49 | artifactId = "composable-routes-processor" 50 | version = composeRoutesVersion 51 | from(components.getByName("java")) 52 | } 53 | } 54 | repositories { 55 | maven { 56 | name = "GitHubPackages" 57 | setUrl("https://maven.pkg.github.com/MatrixDev/ComposableRoutes") 58 | credentials { 59 | username = System.getenv("GITHUB_ACTOR") 60 | password = System.getenv("GITHUB_TOKEN") 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/AnnotationProcessor.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler 2 | 3 | import com.google.auto.service.AutoService 4 | import dev.matrix.compose_routes.ComposableRoute 5 | import dev.matrix.compose_routes.compiler.model.RouteClassName 6 | import dev.matrix.compose_routes.compiler.model.RouteDestination 7 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessor 8 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType 9 | import javax.annotation.processing.AbstractProcessor 10 | import javax.annotation.processing.Processor 11 | import javax.annotation.processing.RoundEnvironment 12 | import javax.annotation.processing.SupportedSourceVersion 13 | import javax.lang.model.SourceVersion 14 | import javax.lang.model.element.ExecutableElement 15 | import javax.lang.model.element.TypeElement 16 | 17 | @AutoService(Processor::class) 18 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 19 | @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) 20 | class AnnotationProcessor : AbstractProcessor() { 21 | 22 | override fun getSupportedSourceVersion() = checkNotNull(SourceVersion.latestSupported()) 23 | override fun getSupportedAnnotationTypes() = mutableSetOf(ComposableRoute::class.java.name) 24 | 25 | override fun process( 26 | annotations: MutableSet, 27 | roundEnvironment: RoundEnvironment, 28 | ): Boolean { 29 | val state = AnnotationProcessorState(processingEnv) 30 | try { 31 | val elements = roundEnvironment.getElementsAnnotatedWith(ComposableRoute::class.java) 32 | .filterIsInstance() 33 | 34 | val routeClasses = HashMap>() 35 | for (element in elements) { 36 | val annotation = element.getAnnotation(ComposableRoute::class.java) 37 | ?: state.error( 38 | "$element is not annotated with ${ComposableRoute::class.simpleName}", 39 | element = element, 40 | ) 41 | 42 | val destination = RouteDestination.from(state, element) 43 | val className = RouteClassName( 44 | className = annotation.generatedClassName, 45 | classPackage = annotation.generatedClassPackage, 46 | ) 47 | 48 | val destinations = routeClasses.getOrPut(className) { HashMap() } 49 | if (destinations.containsKey(destination.name)) { 50 | error("destination ${destination.name} already exists") 51 | } 52 | 53 | destinations[destination.name] = destination 54 | } 55 | 56 | for (entry in routeClasses.entries) { 57 | generateRoutesClass(state, entry.key, entry.value.values) 58 | } 59 | } catch (e: Exception) { 60 | state.error(e.toString()) 61 | } 62 | return true 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/AnnotationProcessorState.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.MemberName 5 | import com.squareup.kotlinpoet.ParameterSpec 6 | import dev.matrix.compose_routes.compiler.serializers.ParcelableSerializer 7 | import dev.matrix.compose_routes.compiler.serializers.SerializableSerializer 8 | import javax.annotation.processing.ProcessingEnvironment 9 | import javax.lang.model.element.Element 10 | import javax.lang.model.element.TypeElement 11 | import javax.lang.model.type.TypeMirror 12 | import javax.tools.Diagnostic 13 | 14 | @Suppress("MemberVisibilityCanBePrivate") 15 | class AnnotationProcessorState(val environment: ProcessingEnvironment) { 16 | val parcelableSerializer = ParcelableSerializer(this) 17 | val serializableSerializer = SerializableSerializer(this) 18 | 19 | val jStringClassName = ClassName("java.lang", "String") 20 | val jStringTypeElement = jStringClassName.toTypeElement() 21 | 22 | val serializableClassName = ClassName("java.io", "Serializable") 23 | val serializableTypeElement = serializableClassName.toTypeElement() 24 | 25 | val parcelableClassName = ClassName("android.os", "Parcelable") 26 | val parcelableTypeElement = parcelableClassName.toTypeElement() 27 | 28 | val composableAnnotation = ClassName("androidx.compose.runtime", "Composable") 29 | val navType = ClassName("androidx.navigation", "NavType") 30 | val navGraphBuilder = ClassName("androidx.navigation", "NavGraphBuilder") 31 | val navBackStackEntry = ClassName("androidx.navigation", "NavBackStackEntry") 32 | val navHostController = ClassName("androidx.navigation", "NavHostController") 33 | val navComposeFun = MemberName("androidx.navigation.compose", "composable") 34 | val navArgumentFun = MemberName("androidx.navigation", "navArgument") 35 | 36 | val navController = ClassName("androidx.navigation", "NavController") 37 | val navControllerTypeElement = navController.toTypeElement() 38 | val defaultNavControllerParameterSpec = ParameterSpec("navController", navController) 39 | 40 | fun error(message: String, element: Element? = null): Nothing { 41 | environment.messager.printMessage(Diagnostic.Kind.ERROR, message, element) 42 | throw Exception(message) 43 | } 44 | 45 | private fun ClassName.toTypeElement(): TypeElement { 46 | return toTypeElementMaybe() ?: error("$this not found") 47 | } 48 | 49 | fun ClassName.toTypeElementMaybe(): TypeElement? { 50 | return environment.elementUtils.getTypeElement(canonicalName) 51 | } 52 | 53 | fun unbox(type: TypeMirror): TypeMirror { 54 | runCatching { 55 | return environment.typeUtils.unboxedType(type) 56 | } 57 | return type 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/RouteClassGenerator.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler 2 | 3 | import com.squareup.kotlinpoet.* 4 | import dev.matrix.compose_routes.compiler.model.RouteClassName 5 | import dev.matrix.compose_routes.compiler.model.RouteDestination 6 | import dev.matrix.compose_routes.compiler.model.nav_type.ParcelableNavType 7 | import dev.matrix.compose_routes.compiler.model.nav_type.SerializableNavType 8 | import javax.tools.StandardLocation 9 | 10 | fun generateRoutesClass( 11 | state: AnnotationProcessorState, 12 | name: RouteClassName, 13 | destinations: Iterable, 14 | ) { 15 | // object 16 | 17 | val typeSpec = TypeSpec.objectBuilder(name.toClassName()) 18 | .addFunction(generateRegisterAll(state, destinations)) 19 | 20 | for (destination in destinations) { 21 | typeSpec.addType(generateDestinationClass(state, destination)) 22 | } 23 | 24 | if (destinations.any { it.arguments.any { arg -> arg.navType is ParcelableNavType } }) { 25 | typeSpec.addFunction(state.parcelableSerializer.generateSerializer()) 26 | typeSpec.addFunction(state.parcelableSerializer.generateDeserializer()) 27 | } 28 | 29 | if (destinations.any { it.arguments.any { arg -> arg.navType is SerializableNavType } }) { 30 | typeSpec.addFunction(state.serializableSerializer.generateSerializer()) 31 | typeSpec.addFunction(state.serializableSerializer.generateDeserializer()) 32 | } 33 | 34 | // file 35 | 36 | val fileSpec = FileSpec.builder(name.classPackage, name.className) 37 | .addType(typeSpec.build()) 38 | 39 | for (destination in destinations) { 40 | fileSpec.addFunction(generateNavigateToExtension(state, name, destination)) 41 | } 42 | 43 | state.environment.filer.createResource( 44 | StandardLocation.SOURCE_OUTPUT, 45 | name.classPackage, 46 | "${name.className}.kt", 47 | ).openWriter().use { fileSpec.build().writeTo(it) } 48 | } 49 | 50 | private fun generateDestinationClass( 51 | state: AnnotationProcessorState, 52 | destination: RouteDestination, 53 | ): TypeSpec { 54 | return TypeSpec.objectBuilder(destination.name) 55 | .addProperty(generatePath(destination)) 56 | .addFunction(generateInvoke(state, destination)) 57 | .addFunction(generateRegister(state, destination)) 58 | .addFunction(generateCompose(state, destination)) 59 | .build() 60 | } 61 | 62 | private fun generatePath(destination: RouteDestination): PropertySpec { 63 | val path = buildString { 64 | append(destination.name) 65 | 66 | for (argument in destination.pathArguments) { 67 | if (argument.typeName.isNullable) { 68 | continue 69 | } 70 | append("/") 71 | append("{${argument.name}}") 72 | } 73 | 74 | var index = 0 75 | for (argument in destination.pathArguments) { 76 | if (!argument.typeName.isNullable) { 77 | continue 78 | } 79 | append(if (++index == 1) "?" else "&") 80 | append("${argument.name}={${argument.name}}") 81 | } 82 | } 83 | return PropertySpec.builder("PATH", STRING, KModifier.CONST) 84 | .initializer("%S", path) 85 | .build() 86 | } 87 | 88 | private fun generateInvoke( 89 | state: AnnotationProcessorState, 90 | destination: RouteDestination, 91 | ): FunSpec { 92 | val spec = FunSpec.builder("invoke") 93 | .returns(STRING) 94 | .addModifiers(KModifier.OPERATOR) 95 | 96 | for (argument in destination.pathArguments) { 97 | spec.addParameter(argument.toParameterSpec()) 98 | } 99 | 100 | if (destination.pathArguments.isEmpty()) { 101 | return spec.addStatement("return %S", destination.name).build() 102 | } 103 | 104 | spec.beginControlFlow("return buildString {") 105 | spec.addStatement("append(%S)", destination.name) 106 | 107 | var optionalAvailable = false 108 | for (argument in destination.pathArguments) { 109 | if (argument.typeName.isNullable) { 110 | optionalAvailable = true 111 | continue 112 | } 113 | 114 | var expression = CodeBlock.of("%L", argument.name) 115 | expression = argument.navType.toNavValue(state, expression) 116 | 117 | spec.addStatement("append('/')", expression) 118 | spec.addStatement("append(%L)", expression) 119 | } 120 | 121 | if (optionalAvailable) { 122 | spec.addStatement("var optionalArgumentIndex = 0") 123 | for (argument in destination.arguments) { 124 | if (!argument.typeName.isNullable) { 125 | continue 126 | } 127 | 128 | var expression = CodeBlock.of("%L", argument.name) 129 | expression = argument.navType.toNavValue(state, expression) 130 | 131 | spec.beginControlFlow("if (%L != null) {", argument.name) 132 | spec.addStatement("append(if (++optionalArgumentIndex == 1) '?' else '&')") 133 | spec.addStatement("append(\"%L=\")", argument.name) 134 | spec.addStatement("append(%L)", expression) 135 | spec.endControlFlow() 136 | } 137 | } 138 | 139 | return spec.endControlFlow().build() 140 | } 141 | 142 | private fun generateRegister( 143 | state: AnnotationProcessorState, 144 | destination: RouteDestination, 145 | ): FunSpec { 146 | val code = CodeBlock.builder() 147 | 148 | code.addStatement("builder.%M(", state.navComposeFun) 149 | code.indent() 150 | code.addStatement("route = PATH,") 151 | code.addStatement("arguments = listOf(") 152 | code.indent() 153 | for (argument in destination.arguments) { 154 | if (argument.isNavController) { 155 | continue 156 | } 157 | code.addStatement("%M(%S) {", state.navArgumentFun, argument.name) 158 | code.indent() 159 | if (argument.typeName.isNullable) { 160 | code.addStatement("nullable = true") 161 | } 162 | code.addStatement("type = %L", argument.navType.getNavType(state)) 163 | code.unindent() 164 | code.addStatement("},") 165 | } 166 | code.unindent() 167 | code.addStatement("),") 168 | code.unindent() 169 | code.addStatement(") {") 170 | code.indent() 171 | 172 | code.add("Compose(it") 173 | if (destination.hasNavController) { 174 | code.add( 175 | ", %L = %L", 176 | state.defaultNavControllerParameterSpec.name, 177 | state.defaultNavControllerParameterSpec.name 178 | ) 179 | } 180 | code.addStatement(")") 181 | 182 | code.unindent() 183 | code.addStatement("}") 184 | 185 | return FunSpec.builder("register") 186 | .addParameter("builder", state.navGraphBuilder) 187 | .also { 188 | if (destination.hasNavController) { 189 | it.addParameter(state.defaultNavControllerParameterSpec) 190 | } 191 | } 192 | .addCode(code.build()) 193 | .build() 194 | } 195 | 196 | private fun generateCompose( 197 | state: AnnotationProcessorState, 198 | destination: RouteDestination, 199 | ): FunSpec { 200 | val code = CodeBlock.builder() 201 | 202 | code.add("%L(", destination.memberName) 203 | if (destination.arguments.isNotEmpty()) { 204 | code.addStatement("") 205 | code.indent() 206 | for (argument in destination.arguments) { 207 | if (argument.isNavController) { 208 | code.addStatement( 209 | "%L = %L,", 210 | argument.name, 211 | state.defaultNavControllerParameterSpec.name, 212 | ) 213 | continue 214 | } 215 | 216 | var expression = argument.navType.getFromBundle(state, argument, "entry.arguments!!") 217 | expression = argument.navType.fromNavValue(state, expression) 218 | 219 | if (argument.typeName.isNullable) { 220 | code.addStatement( 221 | "%L = when (entry.arguments!!.containsKey(%S)) {", 222 | argument.name, 223 | argument.name, 224 | ) 225 | code.indent() 226 | code.addStatement("true -> %L", expression) 227 | code.addStatement("else -> null") 228 | code.unindent() 229 | code.addStatement("},") 230 | } else { 231 | code.addStatement("%L = %L,", argument.name, expression) 232 | } 233 | } 234 | code.unindent() 235 | } 236 | code.add(")") 237 | 238 | return FunSpec.builder("Compose") 239 | .addAnnotation(state.composableAnnotation) 240 | .also { 241 | if (destination.pathArguments.isNotEmpty()) { 242 | return@also 243 | } 244 | it.addAnnotation( 245 | AnnotationSpec.builder(Suppress::class) 246 | .addMember("%S", "UNUSED_PARAMETER") 247 | .build() 248 | ) 249 | } 250 | .addParameter("entry", state.navBackStackEntry) 251 | .also { 252 | if (destination.hasNavController) { 253 | it.addParameter(state.defaultNavControllerParameterSpec) 254 | } 255 | } 256 | .addCode(code.build()) 257 | .build() 258 | } 259 | 260 | private fun generateNavigateToExtension( 261 | state: AnnotationProcessorState, 262 | name: RouteClassName, 263 | destination: RouteDestination, 264 | ): FunSpec { 265 | val code = CodeBlock.builder() 266 | 267 | code.add("navigate(%T.%L(", name.toClassName(), destination.name) 268 | if (destination.pathArguments.isNotEmpty()) { 269 | code.addStatement("") 270 | code.indent() 271 | for (argument in destination.pathArguments) { 272 | code.addStatement("%L = %L,", argument.name, argument.name) 273 | } 274 | code.unindent() 275 | } 276 | code.addStatement("))") 277 | 278 | return FunSpec.builder("navigateTo${destination.name}") 279 | .receiver(state.navHostController) 280 | .also { 281 | for (argument in destination.pathArguments) { 282 | it.addParameter(argument.toParameterSpec()) 283 | } 284 | } 285 | .addCode(code.build()) 286 | .build() 287 | } 288 | 289 | private fun generateRegisterAll( 290 | state: AnnotationProcessorState, 291 | destinations: Iterable, 292 | ): FunSpec { 293 | var hasNavController = false 294 | 295 | val code = CodeBlock.builder() 296 | for (destination in destinations) { 297 | code.add("%L.register(builder", destination.name) 298 | 299 | if (destination.hasNavController) { 300 | hasNavController = true 301 | code.add( 302 | ", %L = %L", 303 | state.defaultNavControllerParameterSpec.name, 304 | state.defaultNavControllerParameterSpec.name, 305 | ) 306 | } 307 | 308 | code.addStatement(")") 309 | } 310 | 311 | return FunSpec.builder("registerAll") 312 | .addParameter("builder", state.navGraphBuilder) 313 | .also { 314 | if (hasNavController) { 315 | it.addParameter(state.defaultNavControllerParameterSpec) 316 | } 317 | } 318 | .addCode(code.build()) 319 | .build() 320 | } 321 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/RouteClassName.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | 5 | data class RouteClassName( 6 | val classPackage: String, 7 | val className: String, 8 | ) { 9 | fun toClassName(): ClassName { 10 | return ClassName(classPackage, className) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/RouteDestination.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model 2 | 3 | import com.squareup.kotlinpoet.MemberName 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import javax.lang.model.element.ExecutableElement 6 | import javax.lang.model.type.TypeKind 7 | 8 | data class RouteDestination( 9 | val name: String, 10 | val memberName: MemberName, 11 | val arguments: List, 12 | ) { 13 | companion object { 14 | fun from(state: AnnotationProcessorState, element: ExecutableElement): RouteDestination { 15 | if (element.typeParameters.isNotEmpty()) { 16 | state.error("${element.simpleName} cannot be generic", element = element) 17 | } 18 | if (element.returnType.kind != TypeKind.VOID) { 19 | state.error("${element.simpleName} must return void", element = element) 20 | } 21 | // TODO error private 22 | 23 | val pkg = state.environment.elementUtils.getPackageOf(element) 24 | val name = element.simpleName.toString() 25 | val packageName = pkg.qualifiedName.toString() 26 | 27 | return RouteDestination( 28 | name = name, 29 | memberName = MemberName(packageName, name), 30 | arguments = element.parameters.map { RouteDestinationArg.from(state, it) }, 31 | ) 32 | } 33 | } 34 | 35 | val hasNavController = arguments.any { it.isNavController } 36 | val pathArguments = arguments.filter { !it.isNavController } 37 | } 38 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/RouteDestinationArg.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model 2 | 3 | import com.squareup.kotlinpoet.ParameterSpec 4 | import com.squareup.kotlinpoet.STRING 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.asTypeName 7 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 8 | import dev.matrix.compose_routes.compiler.model.nav_type.NavControllerNavType 9 | import dev.matrix.compose_routes.compiler.model.nav_type.NavType 10 | import org.jetbrains.annotations.Nullable 11 | import javax.lang.model.element.VariableElement 12 | 13 | data class RouteDestinationArg( 14 | val name: String, 15 | val navType: NavType, 16 | val typeName: TypeName, 17 | ) { 18 | companion object { 19 | fun from(state: AnnotationProcessorState, element: VariableElement): RouteDestinationArg { 20 | val type = NavType.from(state, element) 21 | 22 | val jTypeName = state.unbox(element.asType()) 23 | val kTypeName = when (jTypeName == state.jStringTypeElement.asType()) { 24 | true -> STRING 25 | else -> jTypeName.asTypeName() 26 | }.copy(nullable = element.getAnnotation(Nullable::class.java) != null) 27 | 28 | return RouteDestinationArg( 29 | name = element.simpleName.toString(), 30 | navType = type, 31 | typeName = kTypeName, 32 | ) 33 | } 34 | } 35 | 36 | val isNavController = navType == NavControllerNavType 37 | 38 | fun toParameterSpec(): ParameterSpec { 39 | val spec = ParameterSpec.builder(name, typeName) 40 | if (typeName.isNullable) { 41 | spec.defaultValue("null") 42 | } 43 | return spec.build() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/BooleanNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.model.RouteDestinationArg 6 | 7 | object BooleanNavType : NavType() { 8 | override fun getNavType(state: AnnotationProcessorState): CodeBlock { 9 | return CodeBlock.of("%T.BoolType", state.navType) 10 | } 11 | 12 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 13 | return expression 14 | } 15 | 16 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 17 | return expression 18 | } 19 | 20 | override fun getFromBundle( 21 | state: AnnotationProcessorState, 22 | argument: RouteDestinationArg, 23 | bundleValName: String, 24 | ): CodeBlock { 25 | return CodeBlock.of("%L.getBoolean(%S)", bundleValName, argument.name) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/EnumNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import com.squareup.kotlinpoet.asTypeName 5 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 6 | import dev.matrix.compose_routes.compiler.model.RouteDestinationArg 7 | import javax.lang.model.type.TypeMirror 8 | 9 | class EnumNavType(private val type: TypeMirror) : NavType() { 10 | override fun getNavType(state: AnnotationProcessorState): CodeBlock { 11 | return CodeBlock.of("%T.IntType", state.navType) 12 | } 13 | 14 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 15 | return CodeBlock.of("(%L).ordinal", expression) 16 | } 17 | 18 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 19 | return CodeBlock.of("%T.values()[%L]", type.asTypeName(), expression) 20 | } 21 | 22 | override fun getFromBundle( 23 | state: AnnotationProcessorState, 24 | argument: RouteDestinationArg, 25 | bundleValName: String, 26 | ): CodeBlock { 27 | return CodeBlock.of("%L.getInt(%S)", bundleValName, argument.name) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/FloatNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.model.RouteDestinationArg 6 | import javax.lang.model.type.TypeKind 7 | 8 | class FloatNavType(private val kind: TypeKind) : NavType() { 9 | override fun getNavType(state: AnnotationProcessorState): CodeBlock { 10 | return CodeBlock.of("%T.FloatType", state.navType) 11 | } 12 | 13 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock) = when (kind) { 14 | TypeKind.FLOAT -> { 15 | expression 16 | } 17 | TypeKind.DOUBLE -> { 18 | CodeBlock.of("(%L).toFloat()", expression) 19 | } 20 | else -> state.error("unsupported type, must never happen") 21 | } 22 | 23 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock) = when (kind) { 24 | TypeKind.FLOAT -> { 25 | expression 26 | } 27 | TypeKind.DOUBLE -> { 28 | CodeBlock.of("(%L).toDouble()", expression) 29 | } 30 | else -> state.error("unsupported type, must never happen") 31 | } 32 | 33 | override fun getFromBundle( 34 | state: AnnotationProcessorState, 35 | argument: RouteDestinationArg, 36 | bundleValName: String, 37 | ): CodeBlock { 38 | return CodeBlock.of("%L.getFloat(%S)", bundleValName, argument.name) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/IntegerNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.model.RouteDestinationArg 6 | import javax.lang.model.type.TypeKind 7 | 8 | class IntegerNavType(private val kind: TypeKind) : NavType() { 9 | override fun getNavType(state: AnnotationProcessorState): CodeBlock { 10 | return CodeBlock.of("%T.IntType", state.navType) 11 | } 12 | 13 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock) = when (kind) { 14 | TypeKind.INT -> { 15 | expression 16 | } 17 | TypeKind.BYTE, 18 | TypeKind.SHORT, 19 | TypeKind.LONG, 20 | TypeKind.CHAR -> { 21 | CodeBlock.of("(%L).toInt()", expression) 22 | } 23 | else -> state.error("unsupported type ${kind}, must never happen") 24 | } 25 | 26 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock) = when (kind) { 27 | TypeKind.BYTE -> { 28 | CodeBlock.of("(%L).toByte()", expression) 29 | } 30 | TypeKind.CHAR -> { 31 | CodeBlock.of("(%L).toChar()", expression) 32 | } 33 | TypeKind.SHORT -> { 34 | CodeBlock.of("(%L).toShort()", expression) 35 | } 36 | TypeKind.INT -> { 37 | expression 38 | } 39 | TypeKind.LONG -> { 40 | CodeBlock.of("(%L).toLong()", expression) 41 | } 42 | else -> state.error("unsupported type ${kind}, must never happen") 43 | } 44 | 45 | override fun getFromBundle( 46 | state: AnnotationProcessorState, 47 | argument: RouteDestinationArg, 48 | bundleValName: String, 49 | ): CodeBlock { 50 | return CodeBlock.of("%L.getInt(%S)", bundleValName, argument.name) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/NavControllerNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | 6 | object NavControllerNavType : NavType() { 7 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 8 | return expression 9 | } 10 | 11 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 12 | return expression 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/NavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.model.RouteDestinationArg 6 | import javax.lang.model.element.ElementKind 7 | import javax.lang.model.element.VariableElement 8 | import javax.lang.model.type.DeclaredType 9 | import javax.lang.model.type.TypeKind 10 | 11 | sealed class NavType { 12 | companion object { 13 | fun from(state: AnnotationProcessorState, element: VariableElement): NavType { 14 | val utils = state.environment.typeUtils 15 | val type = state.unbox(element.asType()) 16 | 17 | if (type.kind.isPrimitive) { 18 | return when (type.kind) { 19 | TypeKind.BOOLEAN -> { 20 | BooleanNavType 21 | } 22 | TypeKind.FLOAT, TypeKind.DOUBLE -> { 23 | FloatNavType(type.kind) 24 | } 25 | else -> { 26 | IntegerNavType(type.kind) 27 | } 28 | } 29 | } 30 | 31 | if (type.kind == TypeKind.DECLARED) { 32 | val typeElement = (type as DeclaredType).asElement() 33 | if (typeElement.kind == ElementKind.ENUM) { 34 | return EnumNavType(type) 35 | } 36 | if (typeElement == state.jStringTypeElement) { 37 | return StringNavType 38 | } 39 | } 40 | 41 | if (utils.isAssignable(type, state.navControllerTypeElement.asType())) { 42 | return NavControllerNavType 43 | } 44 | 45 | if (utils.isAssignable(type, state.parcelableTypeElement.asType())) { 46 | return ParcelableNavType(state.parcelableSerializer) 47 | } 48 | 49 | if (utils.isAssignable(type, state.serializableTypeElement.asType())) { 50 | return SerializableNavType(state.serializableSerializer) 51 | } 52 | 53 | state.error("${element.asType()} type not supported", element = element) 54 | } 55 | } 56 | 57 | abstract fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock 58 | abstract fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock 59 | 60 | open fun getNavType(state: AnnotationProcessorState): CodeBlock { 61 | return CodeBlock.of("%T.StringType", state.navType) 62 | } 63 | 64 | open fun getFromBundle( 65 | state: AnnotationProcessorState, 66 | argument: RouteDestinationArg, 67 | bundleValName: String, 68 | ): CodeBlock { 69 | return CodeBlock.of("%L.getString(%S).orEmpty()", bundleValName, argument.name) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/ParcelableNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.serializers.ParcelableSerializer 6 | 7 | class ParcelableNavType(private val serializer: ParcelableSerializer) : NavType() { 8 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 9 | return serializer.serialize(expression) 10 | } 11 | 12 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 13 | return serializer.deserialize(expression) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/SerializableNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.serializers.SerializableSerializer 6 | 7 | class SerializableNavType(private val serializer: SerializableSerializer) : NavType() { 8 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 9 | return serializer.serialize(expression) 10 | } 11 | 12 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 13 | return serializer.deserialize(expression) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/model/nav_type/StringNavType.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.model.nav_type 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | import dev.matrix.compose_routes.compiler.utils.decodeUrl 6 | import dev.matrix.compose_routes.compiler.utils.encodeUrl 7 | 8 | object StringNavType : NavType() { 9 | override fun getNavType(state: AnnotationProcessorState): CodeBlock { 10 | return CodeBlock.of("%T.StringType", state.navType) 11 | } 12 | 13 | override fun toNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 14 | return encodeUrl(expression) 15 | } 16 | 17 | override fun fromNavValue(state: AnnotationProcessorState, expression: CodeBlock): CodeBlock { 18 | return decodeUrl(expression) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/serializers/ParcelableSerializer.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.serializers 2 | 3 | import com.squareup.kotlinpoet.* 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | 6 | class ParcelableSerializer(private val state: AnnotationProcessorState) { 7 | private val base64ClassName = ClassName("android.util", "Base64") 8 | private val parcelClassName = ClassName("android.os", "Parcel") 9 | 10 | fun serialize(expression: CodeBlock): CodeBlock { 11 | return CodeBlock.of("(%L).serializeToString()", expression) 12 | } 13 | 14 | fun deserialize(expression: CodeBlock): CodeBlock { 15 | return CodeBlock.of("(%L).deserializeToParcelable()", expression) 16 | } 17 | 18 | fun generateSerializer(): FunSpec { 19 | return FunSpec.builder("serializeToString") 20 | .receiver(state.parcelableClassName) 21 | .returns(STRING) 22 | .addModifiers(KModifier.PRIVATE) 23 | .addStatement("val parcel = %T.obtain()", parcelClassName) 24 | .beginControlFlow("try") 25 | .addStatement("parcel.writeParcelable(this, 0)") 26 | .addStatement("parcel.setDataPosition(0)") 27 | .addStatement( 28 | "return %T.encodeToString(parcel.marshall(), %T.URL_SAFE)", 29 | base64ClassName, 30 | base64ClassName, 31 | ) 32 | .nextControlFlow("finally") 33 | .addStatement("parcel.recycle()") 34 | .endControlFlow() 35 | .build() 36 | } 37 | 38 | fun generateDeserializer(): FunSpec { 39 | val type = TypeVariableName("T", state.parcelableClassName) 40 | return FunSpec.builder("deserializeToParcelable") 41 | .addTypeVariable(type.copy(reified = true)) 42 | .addModifiers(KModifier.INLINE) 43 | .addModifiers(KModifier.PRIVATE) 44 | .receiver(STRING) 45 | .returns(type) 46 | .addStatement("val bytes = Base64.decode(this, Base64.URL_SAFE)", base64ClassName) 47 | .addStatement("val parcel = %T.obtain()", parcelClassName) 48 | .beginControlFlow("try") 49 | .addStatement("parcel.unmarshall(bytes, 0, bytes.size)") 50 | .addStatement("parcel.setDataPosition(0)") 51 | .addStatement("return parcel.readParcelable(this.javaClass.classLoader)!!") 52 | .nextControlFlow("finally") 53 | .addStatement("parcel.recycle()") 54 | .endControlFlow() 55 | .build() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/serializers/SerializableSerializer.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.serializers 2 | 3 | import com.squareup.kotlinpoet.* 4 | import dev.matrix.compose_routes.compiler.AnnotationProcessorState 5 | 6 | class SerializableSerializer(private val state: AnnotationProcessorState) { 7 | private val oosClassName = ClassName("java.io", "ObjectOutputStream") 8 | private val oisClassName = ClassName("java.io", "ObjectInputStream") 9 | private val bosClassName = ClassName("java.io", "ByteArrayOutputStream") 10 | private val bisClassName = ClassName("java.io", "ByteArrayInputStream") 11 | private val base64ClassName = ClassName("java.util", "Base64") 12 | 13 | fun serialize(expression: CodeBlock): CodeBlock { 14 | return CodeBlock.of("(%L).serializeToString()", expression) 15 | } 16 | 17 | fun deserialize(expression: CodeBlock): CodeBlock { 18 | return CodeBlock.of("(%L).deserializeToSerializable()", expression) 19 | } 20 | 21 | fun generateSerializer(): FunSpec { 22 | return FunSpec.builder("serializeToString") 23 | .receiver(state.serializableClassName) 24 | .returns(STRING) 25 | .addModifiers(KModifier.PRIVATE) 26 | .addStatement("val bos = %T()", bosClassName) 27 | .addStatement("val oos = %T(bos)", oosClassName) 28 | .addStatement("oos.writeObject(this)") 29 | .addStatement("oos.flush()") 30 | .addStatement( 31 | "return %T.getUrlEncoder().encodeToString(bos.toByteArray())", 32 | base64ClassName, 33 | ) 34 | .build() 35 | } 36 | 37 | fun generateDeserializer(): FunSpec { 38 | val type = TypeVariableName("T", state.parcelableClassName) 39 | return FunSpec.builder("deserializeToSerializable") 40 | .addTypeVariable(type.copy(reified = true)) 41 | .addModifiers(KModifier.INLINE) 42 | .addModifiers(KModifier.PRIVATE) 43 | .receiver(STRING) 44 | .returns(type) 45 | .addStatement("val bytes = %T.getUrlDecoder().decode(this)", base64ClassName) 46 | .addStatement("val bis = %T(bytes)", bisClassName) 47 | .addStatement("val ois = %T(bis)", oisClassName) 48 | .addStatement("return ois.readObject() as %T", type) 49 | .build() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /composable-routes-processor/src/main/java/dev/matrix/compose_routes/compiler/utils/UrlEncoder.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes.compiler.utils 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | 6 | private val urlEncoderClassName = ClassName("java.net", "URLEncoder") 7 | private val urlDecoderClassName = ClassName("java.net", "URLDecoder") 8 | 9 | fun encodeUrl(expression: CodeBlock): CodeBlock { 10 | return CodeBlock.of("%T.encode(%L, %S)", urlEncoderClassName, expression, "UTF-8") 11 | } 12 | 13 | fun decodeUrl(expression: CodeBlock): CodeBlock { 14 | return CodeBlock.of("%T.decode(%L, %S)", urlDecoderClassName, expression, "UTF-8") 15 | } 16 | -------------------------------------------------------------------------------- /composable-routes-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /composable-routes-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("kotlin-kapt") 5 | id("kotlin-parcelize") 6 | } 7 | 8 | android { 9 | compileSdk = 31 10 | 11 | defaultConfig { 12 | applicationId = "dev.matrix.compose_routes" 13 | minSdk = 21 14 | targetSdk = 31 15 | versionCode = 1 16 | versionName = "1.0" 17 | } 18 | 19 | buildTypes { 20 | getByName("release") { 21 | isMinifyEnabled = false 22 | proguardFile("proguard-rules.pro") 23 | proguardFile(getDefaultProguardFile("proguard-android.txt")) 24 | } 25 | } 26 | 27 | buildFeatures { 28 | compose = true 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_1_8 33 | targetCompatibility = JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = "1.8" 38 | } 39 | 40 | composeOptions { 41 | kotlinCompilerExtensionVersion = "1.0.5" 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation(project(":composable-routes-lib")) 47 | "kapt"(project(":composable-routes-processor")) 48 | 49 | // AndroidX & Material 50 | implementation("androidx.core:core-ktx:1.7.0") 51 | implementation("androidx.appcompat:appcompat:1.3.1") 52 | implementation("com.google.android.material:material:1.4.0") 53 | 54 | // Compose 55 | implementation("androidx.compose.ui:ui:1.0.5") 56 | implementation("androidx.compose.ui:ui-tooling:1.0.5") 57 | implementation("androidx.compose.foundation:foundation:1.0.5") 58 | implementation("androidx.compose.runtime:runtime-livedata:1.0.5") 59 | implementation("androidx.activity:activity-compose:1.4.0") 60 | 61 | // Compose Controls 62 | implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-rc01") 63 | 64 | // Compose Material 65 | implementation("androidx.compose.material:material:1.0.5") 66 | implementation("androidx.compose.material:material-icons-core:1.0.5") 67 | implementation("androidx.compose.material:material-icons-extended:1.0.5") 68 | 69 | // Compose Navigation 70 | implementation("androidx.navigation:navigation-compose:2.4.0-beta02") 71 | } 72 | -------------------------------------------------------------------------------- /composable-routes-test/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /composable-routes-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/java/dev/matrix/compose_routes/MyScreen.kt: -------------------------------------------------------------------------------- 1 | package dev.matrix.compose_routes 2 | 3 | import android.graphics.PorterDuff 4 | import android.os.Parcelable 5 | import androidx.compose.runtime.Composable 6 | import androidx.navigation.NavController 7 | import androidx.navigation.compose.NavHost 8 | import androidx.navigation.compose.rememberNavController 9 | import kotlinx.parcelize.Parcelize 10 | import java.io.Serializable 11 | 12 | @Parcelize 13 | class ParcelableArg : Parcelable 14 | class SerializableArg : Serializable 15 | enum class EnumArg { A } 16 | 17 | @Composable 18 | @ComposableRoute 19 | fun ScreenNoArgs() { 20 | } 21 | 22 | @Composable 23 | @ComposableRoute 24 | fun ScreenWithString1(arg0: String) { 25 | } 26 | 27 | @Composable 28 | @ComposableRoute 29 | fun ScreenWithString2(arg0: String?) { 30 | } 31 | 32 | @Composable 33 | @ComposableRoute 34 | fun ScreenWithInt(arg0: Int) { 35 | } 36 | 37 | @Composable 38 | @ComposableRoute 39 | fun ScreenWithNavController1(controller: NavController) { 40 | } 41 | 42 | @Composable 43 | @ComposableRoute 44 | fun ScreenWithNavController2(arg0: Int, controller: NavController) { 45 | } 46 | 47 | @Composable 48 | @ComposableRoute 49 | fun ScreenWithSimpleArgs1( 50 | arg0: Boolean, 51 | arg1: Byte, 52 | arg2: Short, 53 | arg3: Int, 54 | arg4: Long, 55 | arg5: Float?, 56 | arg6: Double?, 57 | ) { 58 | } 59 | 60 | @Composable 61 | @ComposableRoute 62 | fun ScreenWithSimpleArgs2( 63 | arg0: Boolean?, 64 | arg1: Byte?, 65 | arg2: Short?, 66 | arg3: Int?, 67 | arg4: Long?, 68 | arg5: Float, 69 | arg6: Double, 70 | ) { 71 | } 72 | 73 | @Composable 74 | @ComposableRoute 75 | fun ScreenWithParcelableArgs1( 76 | arg0: ParcelableArg, 77 | ) { 78 | } 79 | 80 | @Composable 81 | @ComposableRoute 82 | fun ScreenWithParcelableArgs2( 83 | arg0: ParcelableArg?, 84 | ) { 85 | } 86 | 87 | @Composable 88 | @ComposableRoute 89 | fun ScreenWithEnumArgs( 90 | arg1: EnumArg, 91 | arg2: PorterDuff.Mode, 92 | ) { 93 | } 94 | 95 | @Composable 96 | @ComposableRoute 97 | fun ScreenWithSerializableArgs( 98 | arg0: SerializableArg, 99 | ) { 100 | } 101 | 102 | @Composable 103 | fun MainScreen() { 104 | val navController = rememberNavController() 105 | NavHost(navController = navController, startDestination = NavRoutes.ScreenNoArgs.PATH) { 106 | NavRoutes.registerAll(this, navController) 107 | } 108 | navController.navigateToScreenNoArgs() 109 | navController.navigateToScreenWithInt(10) 110 | } 111 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/composable-routes-test/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | -------------------------------------------------------------------------------- /composable-routes-test/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixDev/ComposableRoutes/be38762f54a5b93f5b882f41fd49571f0cdaddf7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Nov 17 15:41:59 EET 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | # configuration file for building snapshots and releases with jitpack.io 2 | jdk: 3 | - openjdk11 4 | before_install: 5 | - ./scripts/prepareJitpackEnvironment.sh 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | maven(url = "https://jitpack.io") 7 | } 8 | } 9 | 10 | rootProject.name = "composable-routes" 11 | 12 | include(":composable-routes-test") 13 | include(":composable-routes-lib") 14 | include(":composable-routes-processor") 15 | --------------------------------------------------------------------------------