Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddf2af11 authored by mitulsheth's avatar mitulsheth
Browse files

Merge branch 'main' of https://gitlab.e.foundation/e/os/maps into bug_long_itinerary_error_message

parents c8f37d09 5562383a
Loading
Loading
Loading
Loading
Loading
+98 −30
Original line number Diff line number Diff line
@@ -25,6 +25,10 @@ import earth.maps.cardinal.data.LocationRepository
import earth.maps.cardinal.data.OrientationRepository
import earth.maps.cardinal.data.RoutingMode
import earth.maps.cardinal.data.room.RoutingProfileRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import javax.inject.Inject
import javax.inject.Singleton

@@ -36,17 +40,39 @@ class FerrostarWrapperRepository @Inject constructor(
    private val orientationRepository: OrientationRepository,
    private val routingProfileRepository: RoutingProfileRepository
) {
    lateinit var walking: FerrostarWrapper
    lateinit var cycling: FerrostarWrapper
    lateinit var driving: FerrostarWrapper
    lateinit var truck: FerrostarWrapper
    lateinit var motorScooter: FerrostarWrapper
    lateinit var motorcycle: FerrostarWrapper
    private val _isInitialized = MutableStateFlow(false)
    val isInitialized = _isInitialized.asStateFlow()

    private var _walking: FerrostarWrapper? = null
    private var _cycling: FerrostarWrapper? = null
    private var _driving: FerrostarWrapper? = null
    private var _truck: FerrostarWrapper? = null
    private var _motorScooter: FerrostarWrapper? = null
    private var _motorcycle: FerrostarWrapper? = null

    val walking: FerrostarWrapper get() = _walking ?: throw IllegalStateException("Walking wrapper not initialized")
    val cycling: FerrostarWrapper get() = _cycling ?: throw IllegalStateException("Cycling wrapper not initialized")
    val driving: FerrostarWrapper get() = _driving ?: throw IllegalStateException("Driving wrapper not initialized")
    val truck: FerrostarWrapper get() = _truck ?: throw IllegalStateException("Truck wrapper not initialized")
    val motorScooter: FerrostarWrapper get() = _motorScooter ?: throw IllegalStateException("MotorScooter wrapper not initialized")
    val motorcycle: FerrostarWrapper get() = _motorcycle ?: throw IllegalStateException("Motorcycle wrapper not initialized")

    private val pendingOptions = mutableMapOf<RoutingMode, RoutingOptions>()

    val androidTtsObserver = AndroidTtsObserver(context)

    /**
     * Suspends the caller until the repository has been initialized with a Valhalla endpoint.
     *
     * Once [_isInitialized] becomes true, this function resumes. If the repository
     * is already initialized, it returns immediately.
     */
    suspend fun awaitInitialization() {
        _isInitialized.filter { it }.first()
    }

    fun setValhallaEndpoint(endpoint: String) {
        walking = FerrostarWrapper(
        _walking = FerrostarWrapper(
            context,
            locationRepository,
            orientationRepository,
@@ -55,7 +81,7 @@ class FerrostarWrapperRepository @Inject constructor(
            androidTtsObserver,
            routingProfileRepository
        )
        cycling = FerrostarWrapper(
        _cycling = FerrostarWrapper(
            context,
            locationRepository,
            orientationRepository,
@@ -64,7 +90,7 @@ class FerrostarWrapperRepository @Inject constructor(
            androidTtsObserver,
            routingProfileRepository
        )
        driving = FerrostarWrapper(
        _driving = FerrostarWrapper(
            context,
            locationRepository,
            orientationRepository,
@@ -73,7 +99,7 @@ class FerrostarWrapperRepository @Inject constructor(
            androidTtsObserver,
            routingProfileRepository
        )
        truck = FerrostarWrapper(
        _truck = FerrostarWrapper(
            context,
            locationRepository,
            orientationRepository,
@@ -82,7 +108,7 @@ class FerrostarWrapperRepository @Inject constructor(
            androidTtsObserver,
            routingProfileRepository
        )
        motorScooter = FerrostarWrapper(
        _motorScooter = FerrostarWrapper(
            context,
            locationRepository,
            orientationRepository,
@@ -91,7 +117,7 @@ class FerrostarWrapperRepository @Inject constructor(
            androidTtsObserver,
            routingProfileRepository
        )
        motorcycle = FerrostarWrapper(
        _motorcycle = FerrostarWrapper(
            context,
            locationRepository,
            orientationRepository,
@@ -100,36 +126,78 @@ class FerrostarWrapperRepository @Inject constructor(
            androidTtsObserver,
            routingProfileRepository
        )

        _isInitialized.value = true

        // Apply pending options
        synchronized(pendingOptions) {
            pendingOptions.forEach { (mode, options) ->
                setOptionsForMode(mode, options)
            }
            pendingOptions.clear()
        }
    }

    /**
     * Updates the routing options for the specified mode by modifying the existing wrapper.
     * Updates the [RoutingOptions] for the specified [mode] by modifying the existing wrapper.
     *
     * If the wrapper for the given [mode] is not yet initialized (i.e., [setValhallaEndpoint]
     * hasn't been called), the options are stored in [pendingOptions] and applied
     * automatically once initialization completes.
     *
     * @param mode The [RoutingMode] to update (e.g., PEDESTRIAN, AUTO).
     * @param routingOptions The new configuration options to apply.
     */
    fun setOptionsForMode(mode: RoutingMode, routingOptions: RoutingOptions) {
        when (mode) {
            RoutingMode.PEDESTRIAN -> walking.setOptions(routingOptions)
            RoutingMode.BICYCLE -> cycling.setOptions(routingOptions)
            RoutingMode.AUTO -> driving.setOptions(routingOptions)
            RoutingMode.TRUCK -> truck.setOptions(routingOptions)
            RoutingMode.MOTOR_SCOOTER -> motorScooter.setOptions(routingOptions)
            RoutingMode.MOTORCYCLE -> motorcycle.setOptions(routingOptions)
            else -> {}
        val wrapper = getWrapperForMode(mode)
        if (wrapper != null) {
            wrapper.setOptions(routingOptions)
        } else {
            synchronized(pendingOptions) {
                pendingOptions[mode] = routingOptions
            }
        }
    }

    /**
     * Resets the routing options for the specified mode to defaults by recreating the wrapper.
     * Resets the [RoutingOptions] for the specified [mode] to their default values.
     *
     * This retrieves the defaults from [routingProfileRepository]. If the [FerrostarWrapper]
     * for this mode is already initialized, the options are applied immediately.
     *
     * If the wrapper is not yet initialized (i.e., [setValhallaEndpoint] hasn't been called),
     * the default options are stored in [pendingOptions] and applied automatically
     * during initialization.
     *
     * @param mode The [RoutingMode] to reset (e.g., PEDESTRIAN, BICYCLE).
     */
    fun resetOptionsToDefaultsForMode(mode: RoutingMode) {
        val defaultOptions = routingProfileRepository.createDefaultOptionsForMode(mode)
        when (mode) {
            RoutingMode.PEDESTRIAN -> walking.setOptions(defaultOptions)
            RoutingMode.BICYCLE -> cycling.setOptions(defaultOptions)
            RoutingMode.AUTO -> driving.setOptions(defaultOptions)
            RoutingMode.TRUCK -> truck.setOptions(defaultOptions)
            RoutingMode.MOTOR_SCOOTER -> motorScooter.setOptions(defaultOptions)
            RoutingMode.MOTORCYCLE -> motorcycle.setOptions(defaultOptions)
            else -> {}
        val wrapper = getWrapperForMode(mode)
        if (wrapper != null) {
            wrapper.setOptions(defaultOptions)
        } else {
            defaultOptions?.let {
                synchronized(pendingOptions) {
                    pendingOptions[mode] = it
                }
            }
        }
    }

    /**
     * Resolves the internal [FerrostarWrapper] instance associated with a specific [RoutingMode].*
     * @param mode The [RoutingMode] for which to retrieve the wrapper.
     * @return The corresponding [FerrostarWrapper] (e.g., [_walking], [_cycling]),
     * or `null` if the mode is unrecognized or the wrapper hasn't been initialized via [setValhallaEndpoint].
     */
    private fun getWrapperForMode(mode: RoutingMode): FerrostarWrapper? = when (mode) {
        RoutingMode.PEDESTRIAN -> _walking
        RoutingMode.BICYCLE -> _cycling
        RoutingMode.AUTO -> _driving
        RoutingMode.TRUCK -> _truck
        RoutingMode.MOTOR_SCOOTER -> _motorScooter
        RoutingMode.MOTORCYCLE -> _motorcycle
        else -> null
    }
}
+33 −10
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ interface AutoOptions {
    val maneuverPenalty: Double?
    val gateCost: Double?
    val privateAccessPenalty: Double?
    val tollBoothCost: Double?
    val useHighways: Double?
    val useTolls: Double?
    val useLivingStreets: Double?
@@ -66,11 +67,12 @@ interface AutoOptions {
 * Routing options for automobile mode.
 */
data class AutoRoutingOptions(
    override val costingType: String = "auto",
    override val costingType: String = COSTING_TYPE_AUTO,

    // Maneuver and access penalties
    override val maneuverPenalty: Double? = null,
    override val gateCost: Double? = null,
    override val maneuverPenalty: Double? = DEFAULT_MANEUVER_PENALTY,
    override val gateCost: Double? = DEFAULT_GATE_COST,
    override val tollBoothCost: Double? = DEFAULT_TOLL_BOOTH_COST,
    override val privateAccessPenalty: Double? = null,

    // Road type preferences (0-1 range)
@@ -88,17 +90,26 @@ data class AutoRoutingOptions(
    // HOV options
    override val excludeUnpaved: Boolean? = null,
    override val excludeCashOnlyTolls: Boolean? = null
) : RoutingOptions(), AutoOptions
) : RoutingOptions(), AutoOptions {

    companion object {
        const val COSTING_TYPE_AUTO = "auto"
        const val DEFAULT_MANEUVER_PENALTY = 25.0
        const val DEFAULT_GATE_COST = 45.0
        const val DEFAULT_TOLL_BOOTH_COST = 30.0
    }
}

/**
 * Routing options for truck mode (extends auto with truck-specific parameters).
 */
data class TruckRoutingOptions(
    override val costingType: String = "truck",
    override val costingType: String = COSTING_TYPE_TRUCK,

    // Basic auto options
    override val maneuverPenalty: Double? = null,
    override val gateCost: Double? = null,
    override val gateCost: Double? = DEFAULT_GATE_COST,
    override val tollBoothCost: Double? = DEFAULT_TOLL_BOOTH_COST,
    override val privateAccessPenalty: Double? = null,
    override val useHighways: Double? = null,
    override val useTolls: Double? = null,
@@ -119,7 +130,14 @@ data class TruckRoutingOptions(
    val axleCount: Int? = null,
    val hazmat: Boolean? = null,
    val useTruckRoute: Double? = null // 0-1 range
) : RoutingOptions(), AutoOptions
) : RoutingOptions(), AutoOptions {

    companion object {
        const val COSTING_TYPE_TRUCK = "truck"
        const val DEFAULT_GATE_COST = 45.0
        const val DEFAULT_TOLL_BOOTH_COST = 30.0
    }
}

/**
 * Routing options for motor scooter mode.
@@ -130,6 +148,7 @@ data class MotorScooterRoutingOptions(
    // Basic auto options
    override val maneuverPenalty: Double? = null,
    override val gateCost: Double? = null,
    override val tollBoothCost: Double? = null,
    override val privateAccessPenalty: Double? = null,
    override val useHighways: Double? = null,
    override val useTolls: Double? = null,
@@ -156,6 +175,7 @@ data class MotorcycleRoutingOptions(
    // Basic auto options
    override val maneuverPenalty: Double? = null,
    override val gateCost: Double? = null,
    override val tollBoothCost: Double? = null,
    override val privateAccessPenalty: Double? = null,
    override val useHighways: Double? = null,
    override val useTolls: Double? = null,
@@ -199,8 +219,7 @@ data class CyclingRoutingOptions(
data class PedestrianRoutingOptions(
    override val costingType: String = "pedestrian",

    // Walking speed
    val walkingSpeed: Double? = null, // km/h
    val walkingSpeed: Double? = WALKING_SPEED_IN_KMH, // km/h

    // Path preferences (factors)
    val walkwayFactor: Double? = null,
@@ -211,4 +230,8 @@ data class PedestrianRoutingOptions(

    // Accessibility options
    val type: PedestrianType? = null
) : RoutingOptions()
) : RoutingOptions() {
    companion object {
        const val WALKING_SPEED_IN_KMH = 4.2
    }
}
+5 −10
Original line number Diff line number Diff line
@@ -36,11 +36,9 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
@@ -112,7 +110,6 @@ import earth.maps.cardinal.routing.RouteRepository
import earth.maps.cardinal.ui.directions.DirectionsScreen
import earth.maps.cardinal.ui.directions.DirectionsViewModel
import earth.maps.cardinal.ui.directions.RouteDisplayHandler
import earth.maps.cardinal.ui.navigation.TurnByTurnNavigationScreen
import earth.maps.cardinal.ui.home.HomeScreen
import earth.maps.cardinal.ui.home.HomeViewModel
import earth.maps.cardinal.ui.home.NearbyScreenContent
@@ -121,6 +118,7 @@ import earth.maps.cardinal.ui.home.OfflineAreasScreen
import earth.maps.cardinal.ui.home.OfflineAreasViewModel
import earth.maps.cardinal.ui.home.TransitScreenContent
import earth.maps.cardinal.ui.home.TransitScreenViewModel
import earth.maps.cardinal.ui.navigation.TurnByTurnNavigationScreen
import earth.maps.cardinal.ui.place.PlaceCardScreen
import earth.maps.cardinal.ui.place.PlaceCardViewModel
import earth.maps.cardinal.ui.saved.ManagePlacesScreen
@@ -1112,13 +1110,10 @@ private fun TurnByTurnRoute(
        }
    }

    val routingMode = routingModeJson?.let {
        try {
            Gson().fromJson(it, RoutingMode::class.java)
        } catch (_: Exception) {
            RoutingMode.AUTO
        }
    } ?: RoutingMode.AUTO
    val routingMode = runCatching {
        RoutingMode.entries.first { it.value.equals(routingModeJson, ignoreCase = true) } ?: RoutingMode.AUTO
    }.getOrElse { RoutingMode.AUTO }


    port?.let { port ->
        TurnByTurnNavigationScreen(
+6 −0
Original line number Diff line number Diff line
@@ -108,6 +108,9 @@ class DirectionsViewModel @Inject constructor(


    suspend fun initializeRoutingMode() {
        // Wait for FerrostarWrapperRepository to be initialized before setting options
        ferrostarWrapperRepository.awaitInitialization()

        // Set initial routing mode from preferences
        selectedRoutingMode = appPreferenceRepository.lastRoutingMode.value.let { modeString ->
            RoutingMode.entries.find { it.value == modeString } ?: RoutingMode.AUTO
@@ -161,6 +164,9 @@ class DirectionsViewModel @Inject constructor(

    private fun fetchDrivingDirections(origin: Place, destination: Place) {
        viewModelScope.launch {
            // Wait for FerrostarWrapperRepository to be initialized
            ferrostarWrapperRepository.awaitInitialization()

            routeStateRepository.setLoading(true)

            try {
+22 −0
Original line number Diff line number Diff line
/*
 *     Cardinal Maps
 *     Copyright (C) 2025 Cardinal Maps Authors
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package earth.maps.cardinal.ui.directions

import earth.maps.cardinal.MainCoroutineRule
@@ -98,6 +116,8 @@ class DirectionsViewModelTest {
        )
        coEvery { mockRoutingProfileRepository.getProfilesForMode(any()) } returns flowOf(emptyList())

        // Mock FerrostarWrapperRepository
        coEvery { mockFerrostarWrapperRepository.awaitInitialization() } returns Unit
        coEvery { mockFerrostarWrapperRepository.resetOptionsToDefaultsForMode(any()) } returns Unit

        viewModel = DirectionsViewModel(
@@ -256,6 +276,8 @@ class DirectionsViewModelTest {
            address = null
        )

        mockFerrostarWrapperRepository.awaitInitialization()

        // Setup mock to throw an exception
        val mockFerrostarWrapper = mockk<FerrostarWrapper>()
        coEvery { mockFerrostarWrapperRepository.driving } returns mockFerrostarWrapper