Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class HomeScreenMapContainerViewModelTest : BaseHiltTest() {
viewModel.onFeatureClicked(features = setOf(LOCATION_OF_INTEREST_FEATURE))
val state = viewModel.processJobMapComponentState().first()
advanceUntilIdle()
val actualGeoJson = (state as JobMapComponentState.LoiSelected).loi.loiReport!!.geoJson
assertThat(state)
.isEqualTo(
JobMapComponentState.LoiSelected(
Expand All @@ -105,7 +106,11 @@ class HomeScreenMapContainerViewModelTest : BaseHiltTest() {
loi = LOCATION_OF_INTEREST,
submissionCount = 0,
showDeleteLoiButton = true,
loiReport = LOCATION_OF_INTEREST_LOI_REPORT.copy(submissionDetails = null),
loiReport =
LOCATION_OF_INTEREST_LOI_REPORT.copy(
submissionDetails = null,
geoJson = actualGeoJson,
),
)
)
)
Expand Down
1 change: 1 addition & 0 deletions core/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ kotlin {
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.groundplatform.domain.model.mutation.Mutation
typealias LoiProperties = Map<String, Any>

const val LOI_NAME_PROPERTY = "name"
const val LOI_ID_PROPERTY = "id"

fun generateProperties(loiName: String? = null): LoiProperties =
loiName?.let { mapOf(LOI_NAME_PROPERTY to it) } ?: mapOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
*/
package org.groundplatform.domain.usecases

import kotlin.time.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.format.char
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
Expand All @@ -27,8 +32,9 @@ import org.groundplatform.domain.model.geometry.LinearRing
import org.groundplatform.domain.model.geometry.MultiPolygon
import org.groundplatform.domain.model.geometry.Point
import org.groundplatform.domain.model.geometry.Polygon
import org.groundplatform.domain.model.locationofinterest.LOI_ID_PROPERTY
import org.groundplatform.domain.model.locationofinterest.LOI_NAME_PROPERTY
import org.groundplatform.domain.model.locationofinterest.LoiProperties
import org.groundplatform.domain.model.locationofinterest.LocationOfInterest
import org.groundplatform.domain.model.locationofinterest.LoiReport
import org.groundplatform.domain.repository.LocationOfInterestRepositoryInterface
import org.groundplatform.domain.repository.SubmissionRepositoryInterface
Expand Down Expand Up @@ -61,23 +67,21 @@ class GetLoiReportUseCase(
submissionRepositoryInterface.getSubmissions(loi).sortedByDescending {
it.lastModified.clientTimestamp
}
val surveyName = surveyRepositoryInterface.getOfflineSurvey(surveyId)?.title.orEmpty()
val submissionDetails =
if (submissions.isNotEmpty()) {
val user = userRepositoryInterface.getAuthenticatedUser()
val surveyName = surveyRepositoryInterface.getOfflineSurvey(surveyId)?.title.orEmpty()
LoiReport.SubmissionDetails(
surveyName = surveyName,
userName = user.displayName,
userEmail = user.email,
submissions = submissions,
)
} else null

return LoiReport(
loiName = loiName,
geoJson =
loi.geometry.toGeoJson(
loi.properties.filter { property -> property.key == LOI_NAME_PROPERTY }
),
geoJson = loi.geometry.toGeoJson(loi = loi, surveyName = surveyName),
submissionDetails = submissionDetails,
)
}
Expand All @@ -86,7 +90,7 @@ class GetLoiReportUseCase(
* Converts a [Geometry] to its GeoJSON representation as defined by
* [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946).
*/
private fun Geometry.toGeoJson(loiProperties: LoiProperties): JsonObject {
private fun Geometry.toGeoJson(loi: LocationOfInterest, surveyName: String): JsonObject {
val geometryJson =
when (this) {
is Point -> geoJsonObject(TYPE_POINT, coordinatesToPosition(coordinates))
Expand All @@ -99,10 +103,12 @@ class GetLoiReportUseCase(
is MultiPolygon ->
geoJsonObject(TYPE_MULTI_POLYGON, JsonArray(polygons.map { polygonToCoordinates(it) }))
}
val properties = getLoiPropertiesMap(loi, surveyName)

return JsonObject(
mapOf(
KEY_TYPE to JsonPrimitive(TYPE_FEATURE),
KEY_PROPERTIES to JsonObject(loiProperties.mapValues { it.value.toJsonPrimitive() }),
KEY_PROPERTIES to JsonObject(properties),
KEY_GEOMETRY to geometryJson,
)
)
Expand Down Expand Up @@ -140,12 +146,45 @@ class GetLoiReportUseCase(
return JsonUnquotedLiteral(value)
}

private fun getLoiPropertiesMap(
loi: LocationOfInterest,
surveyName: String,
): Map<String, JsonPrimitive> = buildMap {
loi.properties[LOI_NAME_PROPERTY]?.let { put(LOI_NAME_PROPERTY, it.toJsonPrimitive()) }
?: loi.properties[LOI_ID_PROPERTY]?.let { put(LOI_ID_PROPERTY, it.toJsonPrimitive()) }
put(KEY_SURVEY, JsonPrimitive(surveyName))
if (loi.isPredefined != true) {
put(KEY_DATE, JsonPrimitive(formatDateTime(loi.lastModified.clientTimestamp)))
}
}

/**
* Formats an epoch-milliseconds timestamp as YYYYMMDD- HH:MM (24h) in the device's local time
* zone (e.g. 20260703 10:00).
*/
internal fun formatDateTime(epochMillis: Long): String {
val instant = Instant.fromEpochMilliseconds(epochMillis)
val local = instant.toLocalDateTime(TimeZone.currentSystemDefault())
val fmt = LocalDateTime.Format {
year()
monthNumber()
day()
char(' ')
hour()
char(':')
minute()
}
return fmt.format(local)
}

private companion object {
const val KEY_TYPE = "type"
const val TYPE_FEATURE = "Feature"
const val KEY_PROPERTIES = "properties"
const val KEY_GEOMETRY = "geometry"
const val KEY_COORDINATES = "coordinates"
const val KEY_SURVEY = "survey"
const val KEY_DATE = "date"
const val TYPE_POINT = "Point"
const val TYPE_LINE_STRING = "LineString"
const val TYPE_POLYGON = "Polygon"
Expand Down
Loading