Skip to content

Commit

Permalink
Merge branch 'release/2.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
cristan committed Nov 30, 2024
2 parents 0a08230 + abba627 commit 8a53f36
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 505 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/android_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ on:
pull_request :
branches : [ main, develop ]
push :
branches : [ main, develop ]
branches :
- main
- develop
- release/*

jobs:
test-feature:
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ android {
applicationId = "nl.ovfietsbeschikbaarheid"
minSdk = 26
targetSdk = 35
versionCode = 15
versionName = "2.1.0"
versionCode = 16
versionName = "2.2.0"

// Only include resources for supported languages
resourceConfigurations += listOf("nl", "en")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class LocationsDataTest {
allLocations.forEach {
val stationName = allStations[it.stationCode]
if (stationName == null && it.stationCode != "BSLC") {
// Triggers at Eindhoven Strijp-S. This is a "new" train station since 2015, so probably from just after stations_nl_2015_08 is made.
// Same applies to Utrecht Vaartsche Rijn: this is made at 2016.
println("Station ${it.stationCode} not found for $it")
error("Station ${it.stationCode} not found for $it")
}
}
allLocations.forEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package nl.ovfietsbeschikbaarheid

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File

/**
* Not a test, rather a tool. Added in the test folder to prevent it being added in the app.
*/
object LocationsCrawler {
private val json = Json {
ignoreUnknownKeys = true
// Pretty print would help in git diff, but it rarely changes and is otherwise a waste of space and CPU cycles
// prettyPrint = true
}

private val httpClient = HttpClient {
install(ContentNegotiation) {
json(json)
}
}

@JvmStatic
fun main(args: Array<String>): Unit = runBlocking {
// NOTE: don't commit your real key
val subscriptionKey = ""

// Alternative: download from https://data.ndovloket.nl/haltes/ It's a big blob of XML and it also contains needless stuff like
// bus stops, but it seems to have a little more info like the street name.
val loaded = httpClient.get("https://gateway.apiportal.ns.nl/nsapp-stations/v3") {
this.header(
"Ocp-Apim-Subscription-Key",
subscriptionKey
)
}.body<StationsDTO>()
val inNlMapped = loaded.payload
.filter { it.country == "NL" }
.map { MyStationDTO(it.id.code, it.names.long, it.location.lat, it.location.lng) }

val asJson = json.encodeToString(inNlMapped)
val file = File("app/src/main/res/raw/stations.json")
file.writeText(asJson)
}
}

@Serializable
data class MyStationDTO(
val code: String,
val name: String,
val lat: Double,
val lng: Double,
)

@Serializable
data class StationsDTO(
val payload: List<StationDTO>
)

@Serializable
data class StationDTO(
val country: String,
val id: StationID,
val names: StationNames,
val location: LocationDTO
)

@Serializable
data class StationID(
val code: String
)

@Serializable
data class StationNames(
val long: String,
val medium: String,
val short: String
)

@Serializable
data class LocationDTO(
val lat: Double,
val lng: Double,
)
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.OVFietsBeschikbaarheid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/nl/ovfietsbeschikbaarheid/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package nl.ovfietsbeschikbaarheid

import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import nl.ovfietsbeschikbaarheid.ui.navigation.Navigation

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT))
setContent {
Navigation()
}
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/nl/ovfietsbeschikbaarheid/dto/Station.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nl.ovfietsbeschikbaarheid.dto

import kotlinx.serialization.Serializable

@Serializable
data class Station(
val code: String,
val name: String,
val lat: Double,
val lng: Double,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ object LocationsMapper {
Pair("Vianen OV-fiets ", "Vianen"),
Pair("OV-fiets - Maastricht", "Maastricht"),

Pair("Openbare fietsenstalling gemeente Groningen : Fietsenstalling Europapark", "Fietsenstalling Europapark"),
Pair("Openbare fietsenstalling gemeente Groningen : Fietsenstalling Europapark", "Groningen Europapark"),
Pair("Gilze Rijen", "Gilze-Rijen"),

// All the other locations at Utrecht start with the word Utrecht, including the other P+Rs. Use the same scheme to make sure they're sorted together.
Pair("P + R Utrecht Science Park (De Uithof)", "Utrecht P+R Science Park (De Uithof)"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,16 @@ class OverviewRepository {
lastResult?.let {
return it
}
return loadLocations()
}

private suspend fun loadLocations(): List<LocationOverviewModel> {
val locations = httpClient.getLocations()
val mapped = LocationsMapper.map(locations)
lastResult = mapped
return mapped
}

suspend fun getResult(): Result<List<LocationOverviewModel>> {
return try {
Result.success(loadLocations())
} catch (e: Exception) {
Result.failure(e)
}
return getAllLocations()
}

suspend fun getAllLocations(): List<LocationOverviewModel> {
return LocationsMapper.map(httpClient.getLocations())
val locations = LocationsMapper.map(httpClient.getLocations())
lastResult = locations
return locations
}

fun getLocations(allLocations: List<LocationOverviewModel>, searchTerm: String): List<LocationOverviewModel> {
fun filterLocations(allLocations: List<LocationOverviewModel>, searchTerm: String): List<LocationOverviewModel> {
return allLocations.filter { it.title.contains(searchTerm, ignoreCase = true) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@ package nl.ovfietsbeschikbaarheid.repository

import android.content.Context
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
import kotlinx.serialization.json.Json
import nl.ovfietsbeschikbaarheid.R
import nl.ovfietsbeschikbaarheid.util.dutchLocale
import nl.ovfietsbeschikbaarheid.dto.Station

class StationRepository(private val context: Context) {
class StationRepository(
private val context: Context
) {
private var cachedStations: Map<String, String>? = null
private var cachedCapacities: Map<String, Int>? = null

private val json = Json

/**
* Returns a map between station code and station name
*/
fun getAllStations(): Map<String, String> {
cachedStations?.let {
return it
}
val stationsStream = context.resources.openRawResource(R.raw.stations_nl_2015_08)
val stations = HashMap<String, String>()
csvReader { delimiter = ';' }.readAll(stationsStream).forEach {
val code = it[1].uppercase(dutchLocale)
val stationName = it[3]
stations[code] = stationName
}
cachedStations = stations
return stations
val stationsStream = context.resources.openRawResource(R.raw.stations)
val inputAsString = stationsStream.bufferedReader().use { it.readText() }
return json.decodeFromString<List<Station>>(inputAsString)
.associate { it.code to it.name }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -155,6 +159,7 @@ private fun DetailsView(
},
)
},
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding ->
when (details) {
ScreenState.FullPageError -> FullPageError(onRetry = onRetry)
Expand Down Expand Up @@ -212,6 +217,9 @@ private fun ActualDetails(
if (details.alternatives.isNotEmpty()) {
Alternatives(details, onAlternativeClicked)
}
Spacer(
Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
Expand Down Expand Up @@ -115,6 +119,7 @@ private fun HomeView(
topBar = {
HomeTopAppBar(onInfoClicked)
},
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding ->
Column(
modifier = Modifier
Expand Down Expand Up @@ -195,6 +200,11 @@ private fun HomeView(
}
}
}
item {
Spacer(
Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)
)
}
}
}
}
Expand Down Expand Up @@ -248,6 +258,11 @@ private fun GpsContent(
onLocationClick(location.location)
}
}
item {
Spacer(
Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)
)
}
}
}
}
Expand Down
Loading

0 comments on commit 8a53f36

Please sign in to comment.