All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.dev.sergiobelda.navigation.compose.extended.NavRoute.kt Maven / Gradle / Ivy

/*
 * Copyright 2024 Sergio Belda
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.sergiobelda.navigation.compose.extended

import androidx.navigation.NamedNavArgument

/**
 * Represents the navigation route to reach some destination. [NavAction.navigate] receives a
 * [NavRoute] object.
 *
 * @param navDestination Navigation destination.
 * @param arguments List of arguments passed in this route.
 */
class NavRoute internal constructor(
    val navDestination: NavDestination,
    private val arguments: Map = emptyMap(),
) where K : NavArgumentKey {

    /**
     * Navigation route. It consists of [navDestination] id and the [arguments] values.
     */
    internal val route: String =
        navDestination.destinationId.addArgumentsValues()

    /**
     * The [arguments] transformed into a Map where the key is the argument string key.
     */
    private val argumentsKeyStringMap: Map
        get() = arguments.mapKeys { it.key.argumentKey }

    /**
     * Returns the part of the route that contains the arguments values.
     */
    private fun String.addArgumentsValues(): String {
        val optionalParameters = navDestination.arguments.filter {
            it.argument.isDefaultValuePresent || it.argument.isNullable
        }
        val parameters = navDestination.arguments.filter {
            !it.argument.isDefaultValuePresent && !it.argument.isNullable
        }

        return this + buildString {
            appendRequiredParameters(parameters)
            appendOptionalParameters(optionalParameters)
        }
    }

    /**
     * Generate the string from the list of required parameters and append if required parameters
     * have been added. For example:
     * - Given no required parameters, the result will be a single '/'.
     * - Given one required parameter, `param1`, the result will be: /value1.
     * - Given multiple required parameters, `param1` and `param2`, the result will be: /value1/value2.
     *
     * @throws IllegalArgumentException if an argument with a key is not present in the arguments.
     */
    private fun StringBuilder.appendRequiredParameters(parameters: List) {
        parameters.takeIf { it.isNotEmpty() }?.forEach { namedNavArgument ->
            if (argumentsKeyStringMap.containsKey(namedNavArgument.name)) {
                append(PARAM_SEPARATOR)
                append(argumentsKeyStringMap[namedNavArgument.name].toString())
            } else {
                throw IllegalArgumentException("${namedNavArgument.name} not present in arguments for destination $navDestination.")
            }
        } ?: append(PARAM_SEPARATOR)
    }

    /**
     * Generate the string from the list of optional parameters and append if optional parameters
     * have been added. For example:
     * - Given no optional parameters, the result will be an empty string.
     * - Given one optional parameter, `param1`, the result will be: ?param1=value1.
     * - Given multiple optional parameters, `param1` and `param2`, the result will be: ?param1=value1¶m2=value2.
     *
     * @throws IllegalArgumentException if an argument with a key is not nullable and its value is null.
     */
    private fun StringBuilder.appendOptionalParameters(optionalParameters: List) {
        optionalParameters.takeIf { it.isNotEmpty() }?.let { list ->
            append(
                list.joinToString(
                    prefix = QUERY_PARAM_PREFIX,
                    separator = QUERY_PARAM_SEPARATOR,
                ) { namedNavArgument ->
                    // Check if the argument is present in the arguments map, if not, check if it has a default value.
                    if (argumentsKeyStringMap.containsKey(namedNavArgument.name)) {
                        val value = argumentsKeyStringMap[namedNavArgument.name]
                        when {
                            value != null -> {
                                "${namedNavArgument.name}=${argumentsKeyStringMap[namedNavArgument.name]}"
                            }

                            !namedNavArgument.argument.isNullable -> {
                                throw IllegalArgumentException("Argument with key ${namedNavArgument.name} is not nullable.")
                            }

                            else -> ""
                        }
                    } else {
                        val defaultValue = namedNavArgument.argument.defaultValue
                        when {
                            defaultValue != null -> {
                                "${namedNavArgument.name}=${namedNavArgument.argument.defaultValue}"
                            }

                            !namedNavArgument.argument.isNullable -> {
                                throw IllegalArgumentException("Argument with key ${namedNavArgument.name} is not nullable.")
                            }

                            else -> ""
                        }
                    }
                }.takeIf { it != QUERY_PARAM_PREFIX }.orEmpty(),
            )
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy