Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Coming soon:
- [TripInfo Request](https://opentransportdata.swiss/en/cookbook/ojptripinforequest/)

## Requirements
Compatible with Android 6+
Compatible with Android 7+

## Integration
To integrate the SDK you have to add following dependency:
Expand Down
4 changes: 3 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ android {

defaultConfig {
applicationId = "ch.opentransportdata"
minSdk = 23
minSdk = 25
targetSdk = 35
versionCode = 1
versionName = "1.0"
Expand Down Expand Up @@ -77,4 +77,6 @@ dependencies {

testImplementation(libs.junit)
debugImplementation(libs.ui.tooling)
//for displaying the map we use Ramani-Maps without modifying the SKD (Source: https://github.com/ramani-maps/ramani-maps)
implementation("org.ramani-maps:ramani-maplibre:0.9.2")
}
12 changes: 12 additions & 0 deletions app/src/main/java/ch/opentransportdata/domain/MapLibreData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ch.opentransportdata.domain

import ch.opentransportdata.ojp.data.dto.response.GeoPositionDto

/**
* Created by Nico Brandenberger on 05.11.2025
*/

data class MapLibreData(
val id: String,
val positions: List<GeoPositionDto>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,19 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import ch.opentransportdata.ojp.BuildConfig
import ch.opentransportdata.ojp.OjpSdk
import ch.opentransportdata.ojp.data.dto.response.GeoPositionDto
import ch.opentransportdata.ojp.data.dto.response.PlaceResultDto
import ch.opentransportdata.presentation.lir.LirScreenComposable
import ch.opentransportdata.presentation.navigation.BottomNavItem
import ch.opentransportdata.presentation.navigation.LocationSearchMask
import ch.opentransportdata.presentation.navigation.TripMap
import ch.opentransportdata.presentation.navigation.TripResults
import ch.opentransportdata.presentation.navigation.TripSearchMask
import ch.opentransportdata.presentation.navigation.navtypes.PlaceResultType
import ch.opentransportdata.presentation.navigation.navtypes.jsonNavType
import ch.opentransportdata.presentation.theme.OJPAndroidSDKTheme
import ch.opentransportdata.presentation.tir.TirScreenComposable
import ch.opentransportdata.presentation.tir.map.MapScreen
import ch.opentransportdata.presentation.tir.result.TripResultScreen
import kotlin.reflect.typeOf

Expand All @@ -48,8 +52,6 @@ class MainActivity : ComponentActivity() {
val bottomNavigationItems = listOf(BottomNavItem.Lir, BottomNavItem.Tir)
OJPAndroidSDKTheme {
val navController = rememberNavController()
// val navBackStackEntry by navController.currentBackStackEntryAsState()
// val currentDestination = navBackStackEntry?.destination
var selectedBottomNavItem by remember { mutableIntStateOf(0) }

Scaffold(
Expand Down Expand Up @@ -111,12 +113,19 @@ class MainActivity : ComponentActivity() {
typeOf<PlaceResultDto>() to PlaceResultType,
)
) { navBackstackEntry ->
val parameters = navBackstackEntry.toRoute<TripResults>()
TripResultScreen(
navHostController = navController,
// originName = parameters.origin.place.placeType?.name() ?: "-",
// viaName = parameters.via?.place?.placeType?.name(),
// destinationName = parameters.destination.place.placeType?.name() ?: "-"
)
}
composable<TripMap>(
typeMap = mapOf(
typeOf<List<GeoPositionDto>>() to jsonNavType<List<GeoPositionDto>>()
)
) { navBackstackEntry ->
val parameters = navBackstackEntry.toRoute<TripMap>()
MapScreen(
coordinates = parameters.coordinates.toList(),
zoom = parameters.zoom
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.opentransportdata.presentation.navigation

import ch.opentransportdata.ojp.data.dto.response.GeoPositionDto
import ch.opentransportdata.ojp.data.dto.response.PlaceResultDto
import kotlinx.serialization.Serializable

Expand All @@ -15,3 +16,9 @@ data class TripResults(
val via: PlaceResultDto? = null,
val destination: PlaceResultDto
)

@Serializable
data class TripMap(
val coordinates: List<GeoPositionDto>,
val zoom: Double,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ch.opentransportdata.presentation.navigation.navtypes

import android.net.Uri
import android.os.Bundle
import androidx.navigation.NavType
import kotlinx.serialization.json.Json

inline fun <reified T> jsonNavType(
isNullableAllowed: Boolean = false,
json: Json = Json
) = object : NavType<T>(isNullableAllowed) {
override fun get(bundle: Bundle, key: String): T? =
bundle.getString(key)?.let { json.decodeFromString<T>(it) }

override fun parseValue(value: String): T =
json.decodeFromString(Uri.decode(value))

override fun serializeAsValue(value: T): String =
Uri.encode(json.encodeToString(value))

override fun put(bundle: Bundle, key: String, value: T) =
bundle.putString(key, json.encodeToString(value))
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ object PreviewData {
name = NameDto(text = "Olten"),
geoPosition = null
),
duration = Duration.parse("PT5M")
duration = Duration.parse("PT5M"),
pathGuidance = null
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
package ch.opentransportdata.presentation.tir.detail

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DirectionsWalk
import androidx.compose.material.icons.outlined.*
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.outlined.ChevronRight
import androidx.compose.material.icons.outlined.FlashOn
import androidx.compose.material.icons.outlined.HideSource
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Shuffle
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material.icons.rounded.WarningAmber
import androidx.compose.material3.*
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -19,7 +44,12 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.PreviewFontScale
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import ch.opentransportdata.ojp.data.dto.response.tir.leg.*
import ch.opentransportdata.ojp.data.dto.response.tir.leg.AttributeDto
import ch.opentransportdata.ojp.data.dto.response.tir.leg.ContinuousLegDto
import ch.opentransportdata.ojp.data.dto.response.tir.leg.LegAlightDto
import ch.opentransportdata.ojp.data.dto.response.tir.leg.LegBoardDto
import ch.opentransportdata.ojp.data.dto.response.tir.leg.TimedLegDto
import ch.opentransportdata.ojp.data.dto.response.tir.leg.TransferLegDto
import ch.opentransportdata.ojp.data.dto.response.tir.situations.PtSituationDto
import ch.opentransportdata.ojp.data.dto.response.tir.situations.PublishingActionDto
import ch.opentransportdata.ojp.data.dto.response.tir.trips.TripDto
Expand All @@ -44,6 +74,8 @@ fun TripDetailScreen(
showSituation: (PublishingActionDto) -> Unit,
requestTripUpdate: (TripDto) -> Unit,
refineTrip: (String) -> Unit,
showMapText: String,
showMap: (String, Boolean) -> Unit
) {
val scrollState = rememberScrollState()
val timedLegs = trip.legs.mapNotNull { it.legType as? TimedLegDto }
Expand All @@ -56,10 +88,10 @@ fun TripDetailScreen(
) {
val consideredSituations = situations?.let { trip.getPtSituationsForTrip(it) } ?: emptyList()
Row {
Button(onClick = {requestTripUpdate(trip)}) {
Button(onClick = { requestTripUpdate(trip) }) {
Text("Update Trip")
}
Button(onClick = {refineTrip(trip.id)}) {
Button(onClick = { refineTrip(trip.id) }) {
Text("Refine Trip")
}
}
Expand Down Expand Up @@ -109,21 +141,41 @@ fun TripDetailScreen(
HorizontalDivider(modifier = Modifier.padding(bottom = 8.dp))
}
trip.legs.forEach { leg ->
when (val legType = leg.legType) {
is TransferLegDto -> TransferLeg(modifier = Modifier.padding(all = 16.dp), leg = legType)
is ContinuousLegDto -> ContinuousLeg(
modifier = Modifier.padding(all = 16.dp),
leg = legType,
isStartLeg = leg == trip.legs.first(),
isLastLeg = leg == trip.legs.last(),
)
var isZoomed = false
Column (
modifier = Modifier.fillMaxWidth(),
) {
when (val legType = leg.legType) {
is TransferLegDto -> {
isZoomed = true
TransferLeg(modifier = Modifier.padding(all = 16.dp), leg = legType)
}
is ContinuousLegDto -> {
isZoomed = true
ContinuousLeg(
modifier = Modifier.padding(all = 16.dp),
leg = legType,
isStartLeg = leg == trip.legs.first(),
isLastLeg = leg == trip.legs.last(),
)
}

is TimedLegDto -> TimedLeg(
leg = legType,
duration = leg.duration,
situations = legType.getPtSituationsForLeg(consideredSituations),
showSituation = showSituation
)
is TimedLegDto -> {
isZoomed = false
TimedLeg(
leg = legType,
duration = leg.duration,
situations = legType.getPtSituationsForLeg(consideredSituations),
showSituation = showSituation,
)
}
}
Button(
modifier = Modifier.padding(vertical = 4.dp).align(alignment = Alignment.CenterHorizontally),
enabled = showMapText == "Show way on map",
onClick = { showMap(leg.id, isZoomed) }) {
Text(text = showMapText)
}
}
}

Expand Down Expand Up @@ -513,7 +565,9 @@ private fun TripDetailScreenPreview() {
situations = emptyList(),
showSituation = {},
requestTripUpdate = {},
refineTrip = {}
refineTrip = {},
showMapText = "Show way on map",
showMap = {_ , _ ->}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.opentransportdata.presentation.tir.map

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import ch.opentransportdata.ojp.data.dto.response.GeoPositionDto
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.Style
import org.ramani.compose.CameraPosition
import org.ramani.compose.MapLibre
import org.ramani.compose.Polyline

/**
* Created by Nico Brandenberger on 04.11.2025
*/

@Composable
fun MapScreen(
coordinates: List<GeoPositionDto>,
zoom: Double = 7.5
) {
val styleUrl = "https://vectortiles.geo.admin.ch/styles/ch.swisstopo.basemap.vt/style.json"
val mapLibrePoints = coordinates.map { LatLng(it.latitude, it.longitude) }
val cameraPosition = rememberSaveable {
mutableStateOf(
CameraPosition(
target = mapLibrePoints.first() ,
zoom = zoom,
)
)
}
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Red
) {
MapLibre(
modifier = Modifier.fillMaxSize(),
styleBuilder = Style.Builder().fromUri(styleUrl),
cameraPosition = cameraPosition.value
) {
Polyline(points = mapLibrePoints, color = "Red", lineWidth = 1.0F)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import ch.opentransportdata.ojp.data.dto.response.tir.situations.PublishingActio
import ch.opentransportdata.ojp.data.dto.response.tir.trips.TripDto
import ch.opentransportdata.presentation.components.TripResultHeader
import ch.opentransportdata.presentation.lir.name
import ch.opentransportdata.presentation.navigation.TripMap
import ch.opentransportdata.presentation.tir.detail.TripDetailScreen
import ch.opentransportdata.presentation.utils.FileReader
import kotlinx.coroutines.delay
Expand All @@ -49,12 +50,14 @@ fun TripResultScreen(
val detailBottomSheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var selectedTrip by remember { mutableStateOf<TripDto?>(null) }
var selectedAction by remember { mutableStateOf<PublishingActionDto?>(null) }
val showMapText = if (viewModel.state.collectAsState().value.mapData.isEmpty()) "Refine Trip first" else "Show way on map"

if (selectedTrip != null) {
ModalBottomSheet(
onDismissRequest = {
coroutineScope.launch {
selectedTrip = null
viewModel.resetMapData()
}
},
sheetState = detailBottomSheet,
Expand All @@ -65,7 +68,19 @@ fun TripResultScreen(
situations = state.value.tripDelivery?.responseContext?.situation?.ptSituation?.filter { it.publishingActions != null },
showSituation = { selectedAction = it },
requestTripUpdate = { viewModel.requestTripUpdate(it) },
refineTrip = { id -> viewModel.refineTrip(id) }
refineTrip = { id -> viewModel.refineTrip(id) },
showMapText = showMapText,
showMap = { id, isZoomed ->
val coordinates = viewModel.state.value.mapData.find { it.id == id }
if (coordinates != null) {
navHostController.navigate(
TripMap(
coordinates = coordinates.positions,
zoom = if (isZoomed) 17.0 else 7.5
)
)
}
}
)
}
}
Expand Down
Loading
Loading