diff --git a/core/test/src/commonTest/kotlin/xyz/ksharma/core/test/viewmodels/SearchStopViewModelTest.kt b/core/test/src/commonTest/kotlin/xyz/ksharma/core/test/viewmodels/SearchStopViewModelTest.kt index 87a73061..1fce79f9 100644 --- a/core/test/src/commonTest/kotlin/xyz/ksharma/core/test/viewmodels/SearchStopViewModelTest.kt +++ b/core/test/src/commonTest/kotlin/xyz/ksharma/core/test/viewmodels/SearchStopViewModelTest.kt @@ -72,7 +72,7 @@ class SearchStopViewModelTest { } } -/* +/* This test is not valid anymore, as we're not calling API. @Test fun `GIVEN search query WHEN SearchTextChanged is triggered and api is success THEN uiState is updated with results`() = runTest { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index f2193550..8db2c2a3 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -17,11 +17,11 @@ import xyz.ksharma.krail.core.analytics.Analytics import xyz.ksharma.krail.core.analytics.AnalyticsScreen import xyz.ksharma.krail.core.analytics.event.AnalyticsEvent import xyz.ksharma.krail.core.analytics.event.trackScreenViewEvent -import xyz.ksharma.krail.core.log.log import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.sandook.SelectProductClassesForStop import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService import xyz.ksharma.krail.trip.planner.ui.state.TransportMode +import xyz.ksharma.krail.trip.planner.ui.state.TransportModeSortOrder import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent @@ -64,16 +64,18 @@ class SearchStopViewModel( log("results: $results")*/ val resultsDb: List = - sandook.selectStops( - stopName = query, - excludeProductClassList = emptyList(), - ).take(50) - resultsDb.forEach { - log("resultsDb [$query]: ${it.stopName}") - } - val stopResults = resultsDb.map { - it.toStopResult() - } + sandook.selectStops(stopName = query, excludeProductClassList = listOf()) + + val stopResults = resultsDb + .map { it.toStopResult() } + .let { + filterProductClasses( + stopResults = it, + excludedProductClasses = listOf(TransportMode.Ferry().productClass).toImmutableList() + ) + } + .let(::prioritiseStops) + .take(50) updateUiState { displayData(stopResults) } }.getOrElse { @@ -84,6 +86,45 @@ class SearchStopViewModel( } } + // TODO - move to another file and add UT for it. Inject and use. + private fun prioritiseStops(stopResults: List): List { + val sortedTransportModes = TransportMode.sortedValues(TransportModeSortOrder.PRIORITY) + val transportModePriorityMap = sortedTransportModes.mapIndexed { index, transportMode -> + transportMode.productClass to index + }.toMap() + + // TODO - these should come from Firebase config and have only these hardcoded as fallback. + val highPriorityStopIds = listOf( + "200060", + "200070", + "200080", + "206010", + "2150106", + "200017", + "200039", + "201016", + "201039", + "201080", + "200066", + "200030", + "200046", + "200050", + + ) + + return stopResults.sortedWith(compareBy( + { stopResult -> + if (stopResult.stopId in highPriorityStopIds) 0 else 1 + }, + { stopResult -> + stopResult.transportModeType.minOfOrNull { + transportModePriorityMap[it.productClass] ?: Int.MAX_VALUE + } ?: Int.MAX_VALUE + }, + { it.stopName } + )) + } + private fun SearchStopState.displayData(stopsResult: List) = copy( stops = stopsResult.toImmutableList(), isLoading = false, @@ -104,6 +145,17 @@ class SearchStopViewModel( } } +fun filterProductClasses( + stopResults: List, + excludedProductClasses: List, +): List { + return stopResults.filter { stopResult -> + val productClasses = stopResult.transportModeType.map { it.productClass } + productClasses.any { it !in excludedProductClasses } + } +} + +/// TODO - move to mapper: private fun SelectProductClassesForStop.toStopResult() = SearchStopState.StopResult( stopId = stopId, stopName = stopName, diff --git a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopFilterByProductClassTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopFilterByProductClassTest.kt new file mode 100644 index 00000000..703cef56 --- /dev/null +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopFilterByProductClassTest.kt @@ -0,0 +1,89 @@ +package xyz.ksharma.krail.trip.planner.ui.searchstop + +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.test.runTest +import xyz.ksharma.krail.trip.planner.ui.state.TransportMode +import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState +import kotlin.test.Test +import kotlin.test.assertEquals + +class StopFilterByProductClassTest { + + @Test + fun `should return stops excluding given product classes`() = runTest { + // Given + val testCases = listOf( + TestCase( + excludedClasses = listOf(1), + expectedStopIds = listOf("10101101", "10101105", "12349", "12356") + ), + TestCase( + excludedClasses = listOf(1, 2), + expectedStopIds = listOf("12349", "12356") + ), + TestCase( + excludedClasses = listOf(5), + expectedStopIds = listOf("10101101", "10101100", "10101105", "12349") + ), + // All product classes are excluded + TestCase( + excludedClasses = listOf(1, 2, 5), + expectedStopIds = listOf() + ) + ) + + testCases.forEach { testCase -> + // When + val actualResults = filterProductClasses( + stopResults = stopResults, + excludedProductClasses = testCase.excludedClasses + ) + + val actualStopIds = actualResults.map { it.stopId } + + // Then + assertEquals(testCase.expectedStopIds.sorted(), actualStopIds.sorted()) + } + } + + private data class TestCase( + val excludedClasses: List, + val expectedStopIds: List, + ) + + companion object { + private val stopResults = listOf( + SearchStopState.StopResult( + stopName = "Town Hall Station", + stopId = "10101101", + transportModeType = listOf( + TransportMode.Train(), + TransportMode.Metro() + ).toImmutableList(), + ), + SearchStopState.StopResult( + stopName = "Wynyard Station", + stopId = "10101100", + transportModeType = listOf(TransportMode.Train()).toImmutableList(), + ), + SearchStopState.StopResult( + stopName = "Metro Only Station", + stopId = "10101105", + transportModeType = listOf(TransportMode.Metro()).toImmutableList(), + ), + SearchStopState.StopResult( + stopName = "Schofields Station", + stopId = "12349", + transportModeType = listOf( + TransportMode.Bus(), + TransportMode.Train() + ).toImmutableList(), + ), + SearchStopState.StopResult( + stopName = "103 ABC Rd, Hallway", + stopId = "12356", + transportModeType = listOf(TransportMode.Bus()).toImmutableList(), + ), + ) + } +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt index 23eaf464..c23d66a9 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -123,9 +123,8 @@ internal class RealSandook(factory: SandookDriverFactory) : Sandook { excludeProductClassList: List, ): List { return nswStopsQueries.selectProductClassesForStop( - stopName, - stopName, - productClass = excludeProductClassList.map { it.toLong() }, + stopId = stopName, + stopName = stopName, ).executeAsList() } diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt index 7b377f00..6d474038 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt @@ -55,8 +55,6 @@ interface Sandook { */ fun selectStops( stopName: String, - excludeProductClassList: List = emptyList(), + excludeProductClassList: List, ): List - - // endregion } diff --git a/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/NswStops.sq b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/NswStops.sq index b4e57585..55371a07 100644 --- a/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/NswStops.sq +++ b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/NswStops.sq @@ -42,14 +42,10 @@ selectProductClassesForStop: SELECT s.*, COALESCE(GROUP_CONCAT(p.productClass), '') AS productClasses FROM NswStops AS s -LEFT JOIN NswStopProductClass AS p ON s.stopId = p.stopId +LEFT JOIN NswStopProductClass AS p + ON s.stopId = p.stopId WHERE ( - s.stopId = ? -- Exact match scenario - OR s.stopName LIKE '%' || ? || '%' -- Partial match scenario -) -AND s.stopId NOT IN ( - SELECT stopId - FROM NswStopProductClass - WHERE productClass IN ? + s.stopId = :stopId -- Use named parameter :stopId + OR s.stopName LIKE '%' || :stopName || '%' -- Use named parameter :stopName ) GROUP BY s.stopId;