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

Commit 3dc9ceac authored by Ellen Poe's avatar Ellen Poe
Browse files

refactor: TransitScreen

parent 496e3a19
Loading
Loading
Loading
Loading
+263 −205
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
@@ -54,7 +55,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
@@ -115,7 +115,23 @@ fun TransitScreenContent(
                bottom = 36.dp
            )
    ) {
        // Departures section
        DeparturesHeader(viewModel, coroutineScope, isRefreshingDepartures.value)
        DeparturesContent(
            didLoadingFail.value,
            isLoading.value,
            departures,
            onRouteClicked,
            use24HourFormat
        )
    }
}

@Composable
private fun DeparturesHeader(
    viewModel: TransitScreenViewModel,
    coroutineScope: kotlinx.coroutines.CoroutineScope,
    isRefreshing: Boolean,
) {
    Row(
        modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically
    ) {
@@ -129,7 +145,7 @@ fun TransitScreenContent(
        )
        // Refresh button for departures
        IconButton(onClick = { coroutineScope.launch { viewModel.refreshData() } }) {
                if (isRefreshingDepartures.value) {
            if (isRefreshing) {
                CircularProgressIndicator(
                    modifier = Modifier.size(24.dp), strokeWidth = 2.dp
                )
@@ -141,15 +157,24 @@ fun TransitScreenContent(
            }
        }
    }
}

        if (didLoadingFail.value) {
@Composable
private fun DeparturesContent(
    didLoadingFail: Boolean,
    isLoading: Boolean,
    departures: List<StopTime>,
    onRouteClicked: (Place) -> Unit,
    use24HourFormat: Boolean,
) {
    if (didLoadingFail) {
        Text(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            text = stringResource(string.failed_to_load_departures)
        )
        } else if (isLoading.value && departures.isEmpty()) {
    } else if (isLoading && departures.isEmpty()) {
        Text(
            modifier = Modifier
                .fillMaxWidth()
@@ -177,8 +202,6 @@ fun TransitScreenContent(
        )
    }
}
}


@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class, ExperimentalTime::class)
@Composable
@@ -187,18 +210,33 @@ fun TransitScreenRouteDepartures(
) {
    // Group departures by route name
    val departuresByRoute = stopTimes.groupBy { it.routeShortName }
    val transitStopString = stringResource(string.transit_stop)

    departuresByRoute.forEach { (routeName, departures) ->
        // Group departures by headsign within each route
        val departuresByHeadsign = departures.groupBy { it.headsign }.map { (key, value) ->
            (key to value.take(1))
        }.toMap()
        val headsigns = departuresByHeadsign.keys.toList().sorted()
        val (departuresByHeadsign, headsigns, places) = prepareHeadsignData(
            departures,
            transitStopString
        )
        TransitRouteItem(
            routeName,
            departures,
            departuresByHeadsign,
            headsigns,
            places,
            onRouteClicked,
            use24HourFormat
        )
    }
}

        val transitStopString = stringResource(string.transit_stop)
        val places = remember(departures) {
            departuresByHeadsign.map { (key, value) ->
                key to value.firstOrNull()?.let { departure ->
private fun prepareHeadsignData(
    departures: List<StopTime>,
    transitStopString: String
): Triple<Map<String, List<StopTime>>, List<String>, Map<String, Place?>> {
    val departuresByHeadsign = departures.groupBy { it.headsign }.mapValues { it.value.take(1) }
    val headsigns = departuresByHeadsign.keys.sorted()
    val places = departuresByHeadsign.mapValues { (key, value) ->
        value.firstOrNull()?.let { departure ->
            Place(
                name = departure.place.name,
                description = transitStopString,
@@ -207,10 +245,23 @@ fun TransitScreenRouteDepartures(
                transitStopId = departure.place.stopId,
            )
        }
            }.toMap()
    }
    return Triple(departuresByHeadsign, headsigns, places)
}

@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class, ExperimentalTime::class)
@Composable
private fun TransitRouteItem(
    routeName: String,
    departures: List<StopTime>,
    departuresByHeadsign: Map<String, List<StopTime>>,
    headsigns: List<String>,
    places: Map<String, Place?>,
    onRouteClicked: (Place) -> Unit,
    use24HourFormat: Boolean,
) {
    val pagerState = rememberPagerState(pageCount = { headsigns.size })

    Box(
        modifier = Modifier
            .fillMaxWidth()
@@ -223,24 +274,7 @@ fun TransitScreenRouteDepartures(
        Card(modifier = Modifier.padding(bottom = dimensionResource(dimen.padding_minor))) {
            Box {
                // Route name at top
                    Row(
                        modifier = Modifier.padding(dimensionResource(dimen.padding_minor))
                    ) {
                        val routeColor = departures.firstOrNull()?.parseRouteColor()
                            ?: MaterialTheme.colorScheme.surfaceVariant
                        Text(
                            text = stringResource(string.square_char),
                            style = MaterialTheme.typography.headlineSmall,
                            fontWeight = FontWeight.Bold,
                            color = routeColor
                        )
                        Spacer(modifier = Modifier.width(dimensionResource(dimen.padding_minor)))
                        Text(
                            text = routeName,
                            style = MaterialTheme.typography.headlineSmall,
                            fontWeight = FontWeight.Bold
                        )
                    }
                RouteNameHeader(routeName, departures)

                Column(modifier = Modifier.fillMaxWidth()) {
                    // Page indicator
@@ -255,10 +289,44 @@ fun TransitScreenRouteDepartures(
                        val departuresForHeadsign =
                            departuresByHeadsign[selectedHeadsign] ?: emptyList()
                        val soonestDeparture = departuresForHeadsign.minByOrNull {
                                val dep = it.place.departure ?: it.place.scheduledDeparture
                                dep ?: ""
                            it.place.departure ?: it.place.scheduledDeparture ?: ""
                        } ?: return@HorizontalPager

                        DepartureItem(selectedHeadsign, soonestDeparture, use24HourFormat)
                    }
                }
            }
        }
    }
}

@Composable
private fun RouteNameHeader(routeName: String, departures: List<StopTime>) {
    Row(modifier = Modifier.padding(dimensionResource(dimen.padding_minor))) {
        val routeColor = departures.firstOrNull()?.parseRouteColor()
            ?: MaterialTheme.colorScheme.surfaceVariant
        Text(
            text = stringResource(string.square_char),
            style = MaterialTheme.typography.headlineSmall,
            fontWeight = FontWeight.Bold,
            color = routeColor
        )
        Spacer(modifier = Modifier.width(dimensionResource(dimen.padding_minor)))
        Text(
            text = routeName,
            style = MaterialTheme.typography.headlineSmall,
            fontWeight = FontWeight.Bold
        )
    }
}

@OptIn(ExperimentalTime::class)
@Composable
private fun DepartureItem(
    selectedHeadsign: String,
    soonestDeparture: StopTime,
    use24HourFormat: Boolean
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
@@ -287,36 +355,33 @@ fun TransitScreenRouteDepartures(
            )
        }
        // Right side: departure time
                                val bestDepartureTime = formatDepartureTime(
                                    soonestDeparture,
                                    use24HourFormat = use24HourFormat
                                )
        DepartureTimeDisplay(soonestDeparture, use24HourFormat)
    }
}

@Composable
private fun RowScope.DepartureTimeDisplay(soonestDeparture: StopTime, use24HourFormat: Boolean) {
    val bestDepartureTime = formatDepartureTime(soonestDeparture, use24HourFormat = use24HourFormat)
    val containerContent = @Composable {
        Row(
                                        modifier = Modifier.padding(
                                            dimensionResource(dimen.padding),
                                        ),
            modifier = Modifier.padding(dimensionResource(dimen.padding)),
            verticalAlignment = Alignment.CenterVertically
        ) {
            if (soonestDeparture.realTime) {
                                            val infiniteTransition =
                                                rememberInfiniteTransition(label = "alpha animation")
                val infiniteTransition = rememberInfiniteTransition(label = "alpha animation")
                val animatedAlpha by infiniteTransition.animateFloat(
                    initialValue = 0.3f,
                    targetValue = 1f,
                    animationSpec = infiniteRepeatable(
                                                    animation = tween(
                                                        durationMillis = 750, easing = LinearEasing
                                                    ), repeatMode = RepeatMode.Reverse
                        animation = tween(durationMillis = 750, easing = LinearEasing),
                        repeatMode = RepeatMode.Reverse
                    ),
                    label = "alpha"
                )
                Text(
                    modifier = Modifier.padding(end = 4.dp),
                    text = stringResource(string.live_indicator_short),
                                                color = MaterialTheme.colorScheme.onSurface.copy(
                                                    alpha = animatedAlpha
                                                ),
                    color = MaterialTheme.colorScheme.onSurface.copy(alpha = animatedAlpha),
                    style = MaterialTheme.typography.headlineSmall
                )
            }
@@ -340,10 +405,3 @@ fun TransitScreenRouteDepartures(
        containerContent()
    }
}
                        }
                    }
                }
            }
        }
    }
}