Skip to content

Commit 8558216

Browse files
authored
Merge pull request #116 from openTdataCH/feature/trip-filter
Feature/trip filter
2 parents 0d5ab9b + cc15e2e commit 8558216

25 files changed

Lines changed: 770 additions & 69 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ch.opentransportdata.domain
2+
3+
/**
4+
* Created by Nico Brandenberger on 18.02.2026
5+
*/
6+
7+
data class VehicleOption(
8+
val vehicleType: String,
9+
val isSelected: Boolean,
10+
)

app/src/main/java/ch/opentransportdata/presentation/components/TripResultHeader.kt

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ package ch.opentransportdata.presentation.components
22

33
import androidx.compose.foundation.layout.Box
44
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
56
import androidx.compose.foundation.layout.padding
67
import androidx.compose.foundation.shape.RoundedCornerShape
78
import androidx.compose.material.icons.Icons
89
import androidx.compose.material.icons.filled.SwapCalls
9-
import androidx.compose.material3.*
10+
import androidx.compose.material3.HorizontalDivider
11+
import androidx.compose.material3.Icon
12+
import androidx.compose.material3.MaterialTheme
13+
import androidx.compose.material3.OutlinedIconButton
14+
import androidx.compose.material3.Surface
15+
import androidx.compose.material3.Text
1016
import androidx.compose.runtime.Composable
1117
import androidx.compose.ui.Alignment
1218
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.res.painterResource
1320
import androidx.compose.ui.tooling.preview.PreviewLightDark
1421
import androidx.compose.ui.unit.dp
22+
import ch.opentransportdata.R
1523
import ch.opentransportdata.presentation.theme.OJPAndroidSDKTheme
1624

1725
/**
@@ -22,7 +30,8 @@ fun TripResultHeader(
2230
modifier: Modifier = Modifier,
2331
originName: String,
2432
destinationName: String,
25-
swapSearch: () -> Unit
33+
swapSearch: () -> Unit,
34+
onSettingsClick: () -> Unit,
2635
) {
2736

2837
Surface(
@@ -44,13 +53,25 @@ fun TripResultHeader(
4453
style = MaterialTheme.typography.titleLarge
4554
)
4655
}
47-
OutlinedIconButton(
56+
Row(
4857
modifier = Modifier
4958
.align(Alignment.CenterEnd)
5059
.padding(end = 8.dp),
51-
onClick = swapSearch
5260
) {
53-
Icon(imageVector = Icons.Default.SwapCalls, contentDescription = null)
61+
OutlinedIconButton(
62+
modifier = Modifier,
63+
onClick = onSettingsClick,
64+
) {
65+
Icon(
66+
painter = painterResource(id = R.drawable.ic_settings),
67+
contentDescription = "Filter Option",
68+
)
69+
}
70+
OutlinedIconButton(
71+
onClick = swapSearch
72+
) {
73+
Icon(imageVector = Icons.Default.SwapCalls, contentDescription = null)
74+
}
5475
}
5576
}
5677
}
@@ -63,7 +84,8 @@ private fun TripResultHeaderPreview() {
6384
TripResultHeader(
6485
originName = "Bern, Eigerplatz",
6586
destinationName = "Basel SBB",
66-
swapSearch = {}
87+
swapSearch = {},
88+
onSettingsClick = {}
6789
)
6890
}
6991
}

app/src/main/java/ch/opentransportdata/presentation/tir/detail/TripDetailScreen.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import ch.opentransportdata.presentation.utils.toFormattedString
6262
import java.time.Duration
6363
import java.time.LocalDateTime
6464
import java.time.format.DateTimeFormatter
65+
import kotlin.math.roundToInt
6566

6667
/**
6768
* Created by Michael Ruppen on 12.07.2024
@@ -75,7 +76,8 @@ fun TripDetailScreen(
7576
requestTripUpdate: (TripDto) -> Unit,
7677
refineTrip: (String) -> Unit,
7778
showMapText: String,
78-
showMap: (String, Boolean) -> Unit
79+
showMap: (String, Boolean) -> Unit,
80+
walkingSpeed: Int,
7981
) {
8082
val scrollState = rememberScrollState()
8183
val timedLegs = trip.legs.mapNotNull { it.legType as? TimedLegDto }
@@ -148,7 +150,7 @@ fun TripDetailScreen(
148150
when (val legType = leg.legType) {
149151
is TransferLegDto -> {
150152
isZoomed = true
151-
TransferLeg(modifier = Modifier.padding(all = 16.dp), leg = legType)
153+
TransferLeg(modifier = Modifier.padding(all = 16.dp),walkingSpeed = walkingSpeed, leg = legType)
152154
}
153155
is ContinuousLegDto -> {
154156
isZoomed = true
@@ -468,6 +470,7 @@ private fun ContinuousLeg(
468470
@Composable
469471
private fun TransferLeg(
470472
modifier: Modifier = Modifier,
473+
walkingSpeed: Int,
471474
leg: TransferLegDto
472475
) {
473476
Row(
@@ -486,6 +489,12 @@ private fun TransferLeg(
486489
style = MaterialTheme.typography.bodyMedium,
487490
color = MaterialTheme.colorScheme.onSurface
488491
)
492+
Text(
493+
modifier = Modifier.padding(start = 4.dp),
494+
text = "Duration ${(leg.duration.toMinutes() / (walkingSpeed / 100.0)).roundToInt()} (min)",
495+
style = MaterialTheme.typography.bodyMedium,
496+
color = MaterialTheme.colorScheme.onSurface
497+
)
489498
}
490499
}
491500

@@ -567,7 +576,8 @@ private fun TripDetailScreenPreview() {
567576
requestTripUpdate = {},
568577
refineTrip = {},
569578
showMapText = "Show way on map",
570-
showMap = {_ , _ ->}
579+
showMap = {_ , _ ->},
580+
walkingSpeed = 100
571581
)
572582
}
573583
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package ch.opentransportdata.presentation.tir.filter
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.layout.size
9+
import androidx.compose.foundation.rememberScrollState
10+
import androidx.compose.foundation.text.KeyboardOptions
11+
import androidx.compose.foundation.verticalScroll
12+
import androidx.compose.material3.ListItem
13+
import androidx.compose.material3.MaterialTheme
14+
import androidx.compose.material3.RadioButton
15+
import androidx.compose.material3.Switch
16+
import androidx.compose.material3.Text
17+
import androidx.compose.material3.TextField
18+
import androidx.compose.runtime.Composable
19+
import androidx.compose.runtime.mutableIntStateOf
20+
import androidx.compose.runtime.remember
21+
import androidx.compose.ui.Alignment
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.text.input.KeyboardType
24+
import androidx.compose.ui.text.style.TextAlign
25+
import androidx.compose.ui.unit.dp
26+
import ch.opentransportdata.domain.VehicleOption
27+
28+
@Composable
29+
fun FilterScreen(
30+
currentWalkingSpeed: Int,
31+
onSelectWalkingSpeed: (Int) -> Unit,
32+
isDirectConnection: Boolean,
33+
onCheckDirectConnection: () -> Unit,
34+
isFewerTransfers: Boolean,
35+
onCheckFewerTransfers: () -> Unit,
36+
vehicleOptions: List<VehicleOption>,
37+
onToggleVehicle: (String) -> Unit,
38+
vehicleSubOptions: List<VehicleOption>,
39+
onToggleSubVehicle: (String) -> Unit,
40+
minDistance: String,
41+
onMinDistanceChange: (String) -> Unit,
42+
maxDistance: String,
43+
onMaxDistanceChange: (String) -> Unit,
44+
minDuration: String,
45+
onMinDurationChange: (String) -> Unit,
46+
maxDuration: String,
47+
onMaxDurationChange: (String) -> Unit,
48+
isBikeTransport: Boolean,
49+
onCheckBikeTransport: () -> Unit,
50+
) {
51+
val selectedWalkingSpeed = remember { mutableIntStateOf(currentWalkingSpeed) }
52+
val walkingSpeedOptions = listOf(50, 75, 100, 150, 200, 400)
53+
54+
Column(
55+
modifier = Modifier
56+
.verticalScroll(rememberScrollState())
57+
.padding(horizontal = 12.dp)
58+
) {
59+
Text(text = "Deviation from average walking speed in percent. (100% == average)", textAlign = TextAlign.Center)
60+
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
61+
walkingSpeedOptions.forEach { walkingSpeed ->
62+
Column(
63+
horizontalAlignment = Alignment.CenterHorizontally
64+
) {
65+
RadioButton(
66+
modifier = Modifier.size(40.dp),
67+
selected = selectedWalkingSpeed.intValue == walkingSpeed,
68+
onClick = {
69+
onSelectWalkingSpeed(walkingSpeed)
70+
selectedWalkingSpeed.value = walkingSpeed
71+
},
72+
)
73+
Text("${walkingSpeed}%")
74+
}
75+
}
76+
}
77+
OptionItem(
78+
text = "Only direct connections",
79+
isSelected = isDirectConnection,
80+
onClick = {
81+
onCheckDirectConnection()
82+
}
83+
)
84+
OptionItem(
85+
text = "Fewer transfers",
86+
enabled = !isDirectConnection,
87+
isSelected = isFewerTransfers && !isDirectConnection,
88+
onClick = {
89+
onCheckFewerTransfers()
90+
},
91+
)
92+
OptionItem(
93+
text = "Bike Transport",
94+
isSelected = isBikeTransport,
95+
onClick = {
96+
onCheckBikeTransport()
97+
}
98+
)
99+
Text("Select your travel modes")
100+
vehicleOptions.forEach { option ->
101+
OptionItem(
102+
text = option.vehicleType,
103+
isSelected = option.isSelected,
104+
onClick = { onToggleVehicle(option.vehicleType) }
105+
)
106+
}
107+
Text("Select your rail sub mode")
108+
vehicleSubOptions.forEach { option ->
109+
OptionItem(
110+
text = option.vehicleType,
111+
isSelected = if (vehicleOptions.first().isSelected) option.isSelected else false,
112+
enabled = vehicleOptions.first().isSelected,
113+
onClick = { onToggleSubVehicle(option.vehicleType) }
114+
)
115+
}
116+
Text("Select your distance")
117+
Column {
118+
DistanceItem(
119+
text = "Min Distance (meter)",
120+
distance = minDistance,
121+
onValueChange = { onMinDistanceChange(it) }
122+
)
123+
DistanceItem(
124+
text = "Max Distance (meter)",
125+
distance = maxDistance,
126+
onValueChange = { onMaxDistanceChange(it) }
127+
)
128+
}
129+
Text("Select your duration")
130+
Column {
131+
DistanceItem(
132+
text = "Min Duration (min)",
133+
distance = minDuration,
134+
onValueChange = { onMinDurationChange(it) }
135+
)
136+
DistanceItem(
137+
text = "Max Duration (min)",
138+
distance = maxDuration,
139+
onValueChange = { onMaxDurationChange(it) }
140+
)
141+
}
142+
}
143+
}
144+
145+
@Composable
146+
private fun OptionItem(
147+
text: String,
148+
isSelected: Boolean,
149+
enabled: Boolean = true,
150+
onClick: () -> Unit,
151+
) {
152+
ListItem(
153+
headlineContent = {
154+
Text(
155+
text = text,
156+
style = MaterialTheme.typography.titleSmall,
157+
color = MaterialTheme.colorScheme.onSurface
158+
)
159+
},
160+
trailingContent = {
161+
Switch(
162+
checked = isSelected,
163+
onCheckedChange = { onClick() },
164+
enabled = enabled
165+
)
166+
}
167+
)
168+
}
169+
170+
@Composable
171+
private fun DistanceItem(
172+
text: String,
173+
distance: String,
174+
onValueChange: (String) -> Unit,
175+
) {
176+
ListItem(
177+
headlineContent = {
178+
Text(
179+
text = text,
180+
style = MaterialTheme.typography.titleSmall,
181+
color = MaterialTheme.colorScheme.onSurface
182+
)
183+
},
184+
trailingContent = {
185+
TextField(
186+
modifier = Modifier.fillMaxWidth(0.5f),
187+
value = distance,
188+
onValueChange = { onValueChange(it) },
189+
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
190+
)
191+
}
192+
)
193+
}

0 commit comments

Comments
 (0)