Skip to content

Commit

Permalink
Merge branch 'release/1.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
cristan committed Oct 22, 2024
2 parents c8022b6 + 477f9ee commit 6645f98
Show file tree
Hide file tree
Showing 16 changed files with 742 additions and 388 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/android_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Android CI

on:
pull_request :
branches : [ main ]
branches : [ main, develop ]
push :
branches : [ main ]
branches : [ main, develop ]

jobs:
test-feature:
Expand Down
10 changes: 8 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 = 34
versionCode = 10
versionName = "1.1"
versionCode = 11
versionName = "1.2"

// Only include resources for supported languages
resourceConfigurations += listOf("nl", "en")
Expand Down Expand Up @@ -48,6 +48,12 @@ android {
}
}

kotlin {
sourceSets.all {
languageSettings.enableLanguageFeature("ExplicitBackingFields")
}
}

dependencies {

implementation(libs.androidx.core.ktx)
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/nl/ovfietsbeschikbaarheid/KtorApiClient.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package nl.ovfietsbeschikbaarheid

import nl.ovfietsbeschikbaarheid.dto.DetailsDTO
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
Expand All @@ -9,6 +8,7 @@ import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import nl.ovfietsbeschikbaarheid.dto.DetailsDTO
import timber.log.Timber

class KtorApiClient {
Expand All @@ -26,9 +26,13 @@ class KtorApiClient {
}
}

suspend fun getDetails(detailUri: String): DetailsDTO {
suspend fun getDetails(detailUri: String): DetailsDTO? {
Timber.i("Loading $detailUri")
return httpClient.get(detailUri).body<DetailsDTO>()
val result = httpClient.get(detailUri)
if (result.status.value == 404) {
return null
}
return result.body<DetailsDTO>()
}

fun close() {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/nl/ovfietsbeschikbaarheid/TestData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ object TestData {
val testLocationOverviewModel = LocationOverviewModel(
"Amersfoort Centraal",
"https://places.ns-mlab.nl/api/v2/places/stationfacility/Zelfservice%20OV-fiets%20uitgiftepunt-nvd001",
1729602804,
"nvd001",
"HVS",
latitude = 52.36599,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ data class Link(
@Serializable
data class LocationExtra(
val locationCode: String,
val fetchTime: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ object LocationsMapper {
"asb003",
"ut018",
"UTVR002",
"ehv004",
"gvc021",
"had002",
"ed001",
"ed002",
// TODO: we might want to add "ktr001": it's added recently, but no updates since
)

fun map(locationsDTO: LocationsDTO): List<LocationOverviewModel> {
Expand Down Expand Up @@ -46,6 +46,7 @@ object LocationsMapper {
LocationOverviewModel(
title = description,
uri = toMap.link.uri,
fetchTime = toMap.extra.fetchTime,
locationCode = toMap.extra.locationCode,
stationCode = toMap.stationCode,
latitude = toMap.lat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package nl.ovfietsbeschikbaarheid.model
data class LocationOverviewModel(
val title: String,
val uri: String,
val fetchTime: Long,
val locationCode: String,
val stationCode: String,
val latitude: Double,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ import nl.ovfietsbeschikbaarheid.ui.theme.Red50
import nl.ovfietsbeschikbaarheid.ui.theme.Yellow50
import nl.ovfietsbeschikbaarheid.ui.view.FullPageError
import nl.ovfietsbeschikbaarheid.ui.view.FullPageLoader
import nl.ovfietsbeschikbaarheid.viewmodel.DetailsContent
import nl.ovfietsbeschikbaarheid.viewmodel.DetailsViewModel
import org.koin.androidx.compose.koinViewModel
import java.net.URLEncoder
import java.time.format.DateTimeFormatter
import java.util.Locale

@Composable
Expand Down Expand Up @@ -128,7 +130,7 @@ fun DetailScreen(
@Composable
private fun DetailsView(
title: String,
details: ScreenState<DetailsModel>,
details: ScreenState<DetailsContent>,
onRetry: () -> Unit,
onPullToRefresh: () -> Unit,
onLocationClicked: (String) -> Unit,
Expand Down Expand Up @@ -156,16 +158,27 @@ private fun DetailsView(
},
) { innerPadding ->
when (details) {
ScreenState.FullPageError -> FullPageError(onRetry)
ScreenState.FullPageError -> FullPageError(onRetry = onRetry)
ScreenState.Loading -> FullPageLoader()
is ScreenState.Loaded<DetailsModel> -> {
PullToRefreshBox(
state = rememberPullToRefreshState(),
modifier = Modifier.padding(innerPadding),
isRefreshing = details.isRefreshing,
onRefresh = onPullToRefresh,
) {
ActualDetails(details.data, onLocationClicked, onAlternativeClicked)
is ScreenState.Loaded<DetailsContent> -> {
when(details.data) {
is DetailsContent.Content -> PullToRefreshBox(
state = rememberPullToRefreshState(),
modifier = Modifier.padding(innerPadding),
isRefreshing = details.isRefreshing,
onRefresh = onPullToRefresh,
) {
ActualDetails(details.data.details, onLocationClicked, onAlternativeClicked)
}
is DetailsContent.NotFound -> {
val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl"))
val formattedDate = formatter.format(details.data.lastFetched)
FullPageError(
title = stringResource(R.string.details_no_data_title),
message = stringResource(R.string.details_no_data_message, details.data.locationTitle, formattedDate),
onRetry = onRetry
)
}
}
}
}
Expand Down Expand Up @@ -321,7 +334,7 @@ private fun Location(details: DetailsModel, onNavigateClicked: (String) -> Unit)
Text("${location.street} ${location.houseNumber}")
Text("${location.postalCode} ${location.city}")
} else {
Text("Coördinaten: ${details.coordinates.latitude}, ${details.coordinates.longitude}")
Text(stringResource(R.string.details_coordinates, details.coordinates.latitude, details.coordinates.longitude))
}
}

Expand Down Expand Up @@ -358,7 +371,7 @@ private fun Location(details: DetailsModel, onNavigateClicked: (String) -> Unit)
// icon = Icons.Filled.,
state = rememberMarkerState(position = details.coordinates),
title = details.description,
snippet = "${details.rentalBikesAvailable ?: "??"} beschikbaar"
snippet = stringResource(R.string.map_available, details.rentalBikesAvailable?.toString() ?: "??")
)
}
}
Expand Down Expand Up @@ -490,7 +503,7 @@ fun DetailsPreview() {
)
DetailsView(
"Amersfoort Mondriaanplein",
ScreenState.Loaded(details),
ScreenState.Loaded(DetailsContent.Content(details)),
{},
{},
{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import nl.ovfietsbeschikbaarheid.ui.theme.OVFietsBeschikbaarheidTheme

@Composable
fun FullPageError(
title: String = stringResource(R.string.full_page_error_title),
message: String = stringResource(R.string.full_page_error_text),
onRetry: () -> Unit
) {
Column(
Expand All @@ -31,15 +33,15 @@ fun FullPageError(
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.full_page_error_title),
text = title,
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
text = stringResource(R.string.full_page_error_text),
text = message,
textAlign = TextAlign.Center
)
Button(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import nl.ovfietsbeschikbaarheid.KtorApiClient
import nl.ovfietsbeschikbaarheid.mapper.DetailsMapper
import nl.ovfietsbeschikbaarheid.model.DetailsModel
Expand All @@ -12,9 +14,10 @@ import nl.ovfietsbeschikbaarheid.repository.OverviewRepository
import nl.ovfietsbeschikbaarheid.repository.StationRepository
import nl.ovfietsbeschikbaarheid.state.ScreenState
import nl.ovfietsbeschikbaarheid.state.setRefreshing
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId

private const val MIN_REFRESH_TIME = 350L

Expand All @@ -24,25 +27,25 @@ class DetailsViewModel(
private val stationRepository: StationRepository
) : ViewModel() {

private val _screenState = mutableStateOf<ScreenState<DetailsModel>>(ScreenState.Loading)
val screenState: State<ScreenState<DetailsModel>> = _screenState
val screenState: State<ScreenState<DetailsContent>>
field = mutableStateOf<ScreenState<DetailsContent>>(ScreenState.Loading)

private val _title = mutableStateOf("")
val title: State<String> = _title
val title: State<String>
field = mutableStateOf("")

private lateinit var overviewModel: LocationOverviewModel

fun setLocationCode(locationCode: String) {
overviewModel = overviewRepository.getAllLocations().find { it.locationCode == locationCode }!!
_title.value = overviewModel.title
title.value = overviewModel.title
viewModelScope.launch {
doRefresh()
}
}

fun onReturnToScreenTriggered() {
if (screenState.value is ScreenState.Loaded) {
_screenState.setRefreshing()
screenState.setRefreshing()
viewModelScope.launch {
doRefresh()
}
Expand All @@ -51,13 +54,13 @@ class DetailsViewModel(

fun onPullToRefresh() {
viewModelScope.launch {
_screenState.setRefreshing()
screenState.setRefreshing()
doRefresh(MIN_REFRESH_TIME)
}
}

fun onRetryClick() {
_screenState.value = ScreenState.Loading
screenState.value = ScreenState.Loading
viewModelScope.launch {
doRefresh()
}
Expand All @@ -67,22 +70,33 @@ class DetailsViewModel(
try {
val before = System.currentTimeMillis()
val details = client.getDetails(overviewModel.uri)
if (details == null) {
val fetchTimeInstant = Instant.ofEpochSecond(1719066494)
val lastFetched = LocalDateTime.ofInstant(fetchTimeInstant, ZoneId.of("Europe/Amsterdam"))!!
screenState.value = ScreenState.Loaded(DetailsContent.NotFound(overviewModel.title, lastFetched))
return
}
val allStations = stationRepository.getAllStations()
val capabilities = stationRepository.getCapacities()
val data = DetailsMapper.convert(details, overviewRepository.getAllLocations(), allStations, capabilities)
val timeElapsed = System.currentTimeMillis() - before
if (timeElapsed < minDelay) {
delay(minDelay - timeElapsed)
}
_screenState.value = ScreenState.Loaded(data)
screenState.value = ScreenState.Loaded(DetailsContent.Content(data))
} catch (e: Exception) {
Timber.e(e)
_screenState.value = ScreenState.FullPageError
screenState.value = ScreenState.FullPageError
}
}

override fun onCleared() {
super.onCleared()
client.close()
}
}

sealed class DetailsContent {
data class NotFound(val locationTitle: String, val lastFetched: LocalDateTime) : DetailsContent()
data class Content(val details: DetailsModel) : DetailsContent()
}
Loading

0 comments on commit 6645f98

Please sign in to comment.