mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
feat: roadstr-accurate road event rendering (kind 1315/1316)
Align Amethyst's road event cards with the roadstr reference clients (https://github.com/jooray/roadstr) for both interoperability and a richer presentation. Interop: - Match roadstr's exact emoji set: road_closure 🚫 (was ⛔) and other ℹ️ (was 📍). The t codes and per-type TTLs already matched. - Make the kind 1316 NIP-31 alt status-dependent ("Roadstr: event confirmed" / "Roadstr: event denied") per the spec, instead of a single "Roadstr: event confirmation". Rendering: - Colored teardrop map pin per category, using roadstr's exact color palette, with the category emoji on the head (new MapPinIcon). - Freshness: fade the report pin to 0.6 under 25% of effective TTL and 0.4 once effectively expired, matching roadstr's opacity rule. - Subtitle meta line: "🕒 23m · expires in 1h" / "· Expired" on reports and "🕒 23m" on confirmations. - Confirmations get a green ✅ / red ❌ status pin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017tYbcy4UGWxqQbcycyL7Yd
This commit is contained in:
+23
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.location
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -27,6 +28,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -51,6 +54,11 @@ private const val DEFAULT_ZOOM = 16.0
|
||||
* servers, which requires the app's package name as the User-Agent (set on
|
||||
* [Configuration] below) — without it OSM returns HTTP 403.
|
||||
*
|
||||
* When [pinColor] and [pinEmoji] are supplied the marker becomes a colored
|
||||
* teardrop with the category emoji on it (see [roadEventPinBitmap]); otherwise
|
||||
* osmdroid's default pin is used. [pinAlpha] fades the marker to signal
|
||||
* freshness (e.g. an event close to its effective expiry).
|
||||
*
|
||||
* Pan/zoom stay enabled, but a touch listener asks the parent to stop
|
||||
* intercepting gestures while the finger is on the map, so dragging the map
|
||||
* pans it instead of scrolling the surrounding feed.
|
||||
@@ -61,10 +69,23 @@ fun LocationPreviewMap(
|
||||
longitude: Double,
|
||||
modifier: Modifier = Modifier,
|
||||
zoom: Double = DEFAULT_ZOOM,
|
||||
pinColor: Color? = null,
|
||||
pinEmoji: String? = null,
|
||||
pinAlpha: Float = 1f,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
val markerIcon =
|
||||
remember(pinColor, pinEmoji) {
|
||||
if (pinColor != null && pinEmoji != null) {
|
||||
val bitmap = roadEventPinBitmap(pinEmoji, pinColor.toArgb(), context.resources.displayMetrics.density)
|
||||
BitmapDrawable(context.resources, bitmap)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val mapView =
|
||||
remember(context) {
|
||||
// Must be set before the MapView is created so OSM tile requests
|
||||
@@ -119,6 +140,8 @@ fun LocationPreviewMap(
|
||||
position = point
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
setInfoWindow(null)
|
||||
markerIcon?.let { icon = it }
|
||||
alpha = pinAlpha
|
||||
}
|
||||
map.overlays.add(marker)
|
||||
map.invalidate()
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.location
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Draws a Roadstr-style map pin: a colored teardrop in [colorArgb] with [emoji]
|
||||
* centered on its head, a white ring, and a soft drop shadow.
|
||||
*
|
||||
* The pin's tip sits at the bottom-center of the returned bitmap, so the
|
||||
* osmdroid marker should be anchored at (0.5, 1.0) — the geographic point then
|
||||
* lands exactly under the tip.
|
||||
*
|
||||
* Matches the visual language of the roadstr reference clients
|
||||
* (<https://github.com/jooray/roadstr>): the same per-category color is used so
|
||||
* a pin reads the same across implementations.
|
||||
*/
|
||||
fun roadEventPinBitmap(
|
||||
emoji: String,
|
||||
colorArgb: Int,
|
||||
density: Float,
|
||||
): Bitmap {
|
||||
fun dp(value: Float) = value * density
|
||||
|
||||
val head = dp(34f) // head diameter
|
||||
val radius = head / 2f
|
||||
val tail = dp(12f) // pointer height below the head
|
||||
val pad = dp(5f) // room for the drop shadow
|
||||
val ringWidth = dp(2.5f)
|
||||
|
||||
val width = (head + pad * 2).roundToInt()
|
||||
val height = (head + tail + pad * 2).roundToInt()
|
||||
val cx = width / 2f
|
||||
val cy = pad + radius
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
// Head circle + pointer drawn as a single path so they share one shadow
|
||||
// instead of seaming where the triangle meets the circle.
|
||||
val body =
|
||||
Path().apply {
|
||||
addCircle(cx, cy, radius, Path.Direction.CW)
|
||||
val mouth = radius * 0.7f
|
||||
moveTo(cx - mouth, cy + radius * 0.55f)
|
||||
lineTo(cx + mouth, cy + radius * 0.55f)
|
||||
lineTo(cx, pad + head + tail)
|
||||
close()
|
||||
}
|
||||
|
||||
val fill =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = colorArgb
|
||||
style = Paint.Style.FILL
|
||||
setShadowLayer(dp(3f), 0f, dp(2f), 0x80000000.toInt())
|
||||
}
|
||||
canvas.drawPath(body, fill)
|
||||
|
||||
val ring =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = ringWidth
|
||||
}
|
||||
canvas.drawCircle(cx, cy, radius - ringWidth / 2f, ring)
|
||||
|
||||
// Color emoji fonts ignore the paint color, so the glyph keeps its own hue.
|
||||
val text =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = head * 0.58f
|
||||
}
|
||||
val metrics = text.fontMetrics
|
||||
val baseline = cy - (metrics.ascent + metrics.descent) / 2f
|
||||
canvas.drawText(emoji, cx, baseline, text)
|
||||
|
||||
return bitmap
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -31,12 +32,16 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.experimental.roadstr.confirmation.RoadEventConfirmationEvent
|
||||
@@ -44,15 +49,20 @@ import com.vitorpamplona.quartz.experimental.roadstr.confirmation.tags.RoadEvent
|
||||
import com.vitorpamplona.quartz.experimental.roadstr.report.RoadEventReportEvent
|
||||
import com.vitorpamplona.quartz.experimental.roadstr.report.tags.RoadEventType
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.toGeoHash
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
/** Emoji marker for a road event category — keeps the card icon-light and locale-independent. */
|
||||
/**
|
||||
* Emoji marker for a road event category. Mirrors the icon set of the roadstr
|
||||
* reference clients (<https://github.com/jooray/roadstr>) so a report reads the
|
||||
* same across implementations; keeps the card icon-light and locale-independent.
|
||||
*/
|
||||
private fun RoadEventType.emoji(): String =
|
||||
when (this) {
|
||||
RoadEventType.POLICE -> "👮"
|
||||
RoadEventType.SPEED_CAMERA -> "📷"
|
||||
RoadEventType.TRAFFIC_JAM -> "🚗"
|
||||
RoadEventType.ACCIDENT -> "💥"
|
||||
RoadEventType.ROAD_CLOSURE -> "⛔"
|
||||
RoadEventType.ROAD_CLOSURE -> "🚫"
|
||||
RoadEventType.CONSTRUCTION -> "🚧"
|
||||
RoadEventType.HAZARD -> "⚠️"
|
||||
RoadEventType.ROAD_CONDITION -> "🛣️"
|
||||
@@ -60,7 +70,29 @@ private fun RoadEventType.emoji(): String =
|
||||
RoadEventType.FOG -> "🌫️"
|
||||
RoadEventType.ICE -> "🧊"
|
||||
RoadEventType.ANIMAL -> "🦌"
|
||||
RoadEventType.OTHER -> "📍"
|
||||
RoadEventType.OTHER -> "ℹ️"
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-category pin color. Uses the exact palette from the roadstr reference
|
||||
* clients (<https://github.com/jooray/roadstr>) so a pin's color carries the
|
||||
* same meaning across the network.
|
||||
*/
|
||||
private fun RoadEventType.color(): Color =
|
||||
when (this) {
|
||||
RoadEventType.POLICE -> Color(0xFF0000FF)
|
||||
RoadEventType.SPEED_CAMERA -> Color(0xFF800080)
|
||||
RoadEventType.TRAFFIC_JAM -> Color(0xFFFF8C00)
|
||||
RoadEventType.ACCIDENT -> Color(0xFFFF0000)
|
||||
RoadEventType.ROAD_CLOSURE -> Color(0xFF8B0000)
|
||||
RoadEventType.CONSTRUCTION -> Color(0xFFFFD700)
|
||||
RoadEventType.HAZARD -> Color(0xFFFF4500)
|
||||
RoadEventType.ROAD_CONDITION -> Color(0xFF4682B4)
|
||||
RoadEventType.POTHOLE -> Color(0xFF795548)
|
||||
RoadEventType.FOG -> Color(0xFF9E9E9E)
|
||||
RoadEventType.ICE -> Color(0xFF00CED1)
|
||||
RoadEventType.ANIMAL -> Color(0xFF4CAF50)
|
||||
RoadEventType.OTHER -> Color(0xFF808080)
|
||||
}
|
||||
|
||||
private fun RoadEventType.labelRes(): Int =
|
||||
@@ -91,10 +123,13 @@ private fun RoadEventType.labelRes(): Int =
|
||||
@Composable
|
||||
fun RenderRoadEventReport(baseNote: Note) {
|
||||
val noteEvent = baseNote.event as? RoadEventReportEvent ?: return
|
||||
val context = LocalContext.current
|
||||
|
||||
val type = remember(noteEvent) { noteEvent.roadEventType() }
|
||||
val comment = remember(noteEvent) { noteEvent.content.trim() }
|
||||
val point = remember(noteEvent) { noteEvent.roadEventPoint() }
|
||||
val freshness = remember(noteEvent) { noteEvent.freshnessAlpha() }
|
||||
val meta = remember(noteEvent) { noteEvent.metaLine(context) }
|
||||
|
||||
Column(MaterialTheme.colorScheme.replyModifier.padding(10.dp)) {
|
||||
val title =
|
||||
@@ -106,6 +141,13 @@ fun RenderRoadEventReport(baseNote: Note) {
|
||||
|
||||
Text(text = title, style = MaterialTheme.typography.titleMedium)
|
||||
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
text = meta,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
if (comment.isNotEmpty()) {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
@@ -118,7 +160,12 @@ fun RenderRoadEventReport(baseNote: Note) {
|
||||
|
||||
if (point != null) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
RoadEventMap(point)
|
||||
RoadEventMap(
|
||||
point = point,
|
||||
pinColor = type?.color(),
|
||||
pinEmoji = type?.emoji(),
|
||||
pinAlpha = freshness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,30 +179,33 @@ fun RenderRoadEventReport(baseNote: Note) {
|
||||
@Composable
|
||||
fun RenderRoadEventConfirmation(baseNote: Note) {
|
||||
val noteEvent = baseNote.event as? RoadEventConfirmationEvent ?: return
|
||||
val context = LocalContext.current
|
||||
|
||||
val status = remember(noteEvent) { noteEvent.status() }
|
||||
val point = remember(noteEvent) { noteEvent.roadEventPoint() }
|
||||
val age = remember(noteEvent) { timeAgoNoDot(noteEvent.createdAt, context) }
|
||||
|
||||
val denied = status == RoadEventStatus.NO_LONGER_THERE
|
||||
val emoji = if (denied) "❌" else "✅"
|
||||
val pinColor = if (denied) Color(0xFFF44336) else Color(0xFF4CAF50)
|
||||
|
||||
Column(MaterialTheme.colorScheme.replyModifier.padding(10.dp)) {
|
||||
val emoji = if (status == RoadEventStatus.NO_LONGER_THERE) "❌" else "✅"
|
||||
val titleRes =
|
||||
if (status == RoadEventStatus.NO_LONGER_THERE) {
|
||||
R.string.road_event_denied
|
||||
} else {
|
||||
R.string.road_event_confirmed
|
||||
}
|
||||
val titleRes = if (denied) R.string.road_event_denied else R.string.road_event_confirmed
|
||||
|
||||
Text(text = "$emoji ${stringResource(titleRes)}", style = MaterialTheme.typography.titleMedium)
|
||||
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "🕒 $age",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text =
|
||||
stringResource(
|
||||
if (status == RoadEventStatus.NO_LONGER_THERE) {
|
||||
R.string.road_event_denies_report
|
||||
} else {
|
||||
R.string.road_event_confirms_report
|
||||
},
|
||||
if (denied) R.string.road_event_denies_report else R.string.road_event_confirms_report,
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
@@ -163,18 +213,30 @@ fun RenderRoadEventConfirmation(baseNote: Note) {
|
||||
|
||||
if (point != null) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
RoadEventMap(point)
|
||||
RoadEventMap(
|
||||
point = point,
|
||||
pinColor = pinColor,
|
||||
pinEmoji = emoji,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A rounded OpenStreetMap preview with a pin at the road event's [point]. */
|
||||
/** A rounded OpenStreetMap preview with a colored emoji pin at the road event's [point]. */
|
||||
@Composable
|
||||
private fun RoadEventMap(point: Pair<Double, Double>) {
|
||||
private fun RoadEventMap(
|
||||
point: Pair<Double, Double>,
|
||||
pinColor: Color? = null,
|
||||
pinEmoji: String? = null,
|
||||
pinAlpha: Float = 1f,
|
||||
) {
|
||||
LocationPreviewMap(
|
||||
latitude = point.first,
|
||||
longitude = point.second,
|
||||
modifier = Modifier.clip(RoundedCornerShape(8.dp)),
|
||||
pinColor = pinColor,
|
||||
pinEmoji = pinEmoji,
|
||||
pinAlpha = pinAlpha,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -198,3 +260,51 @@ private fun resolveRoadEventPoint(
|
||||
val decoded = runCatching { finest.toGeoHash() }.getOrNull() ?: return null
|
||||
return decoded.centerLat to decoded.centerLon
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker opacity by freshness, matching the roadstr clients: full while the
|
||||
* report has plenty of life left, dimmed to 0.6 once under 25% of its effective
|
||||
* TTL remains, and faded to 0.4 once effectively expired. Returns 1f when the
|
||||
* type (and therefore the TTL) is unknown.
|
||||
*/
|
||||
private fun RoadEventReportEvent.freshnessAlpha(now: Long = TimeUtils.now()): Float {
|
||||
val expiryAt = effectiveExpirationAt() ?: return 1f
|
||||
val total = expiryAt - createdAt
|
||||
if (total <= 0L) return 1f
|
||||
|
||||
val fraction = (expiryAt - now).toFloat() / total
|
||||
return when {
|
||||
fraction <= 0f -> 0.4f
|
||||
fraction < 0.25f -> 0.6f
|
||||
else -> 1f
|
||||
}
|
||||
}
|
||||
|
||||
/** "🕒 23m · expires in 1h" (or "· expired") for the report card subtitle. */
|
||||
private fun RoadEventReportEvent.metaLine(
|
||||
context: Context,
|
||||
now: Long = TimeUtils.now(),
|
||||
): String {
|
||||
val age = "🕒 ${timeAgoNoDot(createdAt, context)}"
|
||||
val expiryAt = effectiveExpirationAt() ?: return age
|
||||
|
||||
val remaining = expiryAt - now
|
||||
val expiry =
|
||||
if (remaining <= 0L) {
|
||||
stringRes(context, R.string.road_event_expired)
|
||||
} else {
|
||||
stringRes(context, R.string.road_event_expires_in, formatDuration(context, remaining))
|
||||
}
|
||||
return "$age · $expiry"
|
||||
}
|
||||
|
||||
/** Compact forward duration ("2d" / "3h" / "45m") reusing the time-ago unit strings. */
|
||||
private fun formatDuration(
|
||||
context: Context,
|
||||
seconds: Long,
|
||||
): String =
|
||||
when {
|
||||
seconds >= TimeUtils.ONE_DAY -> "${seconds / TimeUtils.ONE_DAY}${stringRes(context, R.string.d)}"
|
||||
seconds >= TimeUtils.ONE_HOUR -> "${seconds / TimeUtils.ONE_HOUR}${stringRes(context, R.string.h)}"
|
||||
else -> "${(seconds / TimeUtils.ONE_MINUTE).coerceAtLeast(1)}${stringRes(context, R.string.m)}"
|
||||
}
|
||||
|
||||
@@ -3464,6 +3464,8 @@
|
||||
<string name="road_event_denied">No longer there</string>
|
||||
<string name="road_event_confirms_report">Confirms a road report</string>
|
||||
<string name="road_event_denies_report">Reports a road event cleared</string>
|
||||
<string name="road_event_expires_in">Expires in %1$s</string>
|
||||
<string name="road_event_expired">Expired</string>
|
||||
<string name="goal_amount_label">Goal amount (sats)</string>
|
||||
<string name="goal_amount_placeholder">100000</string>
|
||||
<string name="goal_description_label">Describe your goal</string>
|
||||
|
||||
+9
-2
@@ -89,7 +89,14 @@ class RoadEventConfirmationEvent(
|
||||
|
||||
companion object {
|
||||
const val KIND = 1316
|
||||
const val ALT_DESCRIPTION = "Roadstr: event confirmation"
|
||||
|
||||
/** NIP-31 fallback for a confirmation (`still_there`), per the roadstr spec. */
|
||||
const val ALT_CONFIRMED = "Roadstr: event confirmed"
|
||||
|
||||
/** NIP-31 fallback for a denial (`no_longer_there`), per the roadstr spec. */
|
||||
const val ALT_DENIED = "Roadstr: event denied"
|
||||
|
||||
fun altDescription(status: RoadEventStatus) = if (status == RoadEventStatus.NO_LONGER_THERE) ALT_DENIED else ALT_CONFIRMED
|
||||
|
||||
fun build(
|
||||
reportId: HexKey,
|
||||
@@ -106,7 +113,7 @@ class RoadEventConfirmationEvent(
|
||||
coordinates(latitude, longitude)
|
||||
}
|
||||
expiration(createdAt + RoadEventReportEvent.RELAY_TTL_SECONDS)
|
||||
alt(ALT_DESCRIPTION)
|
||||
alt(altDescription(status))
|
||||
initializer()
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -69,7 +69,7 @@ class RoadEventTest {
|
||||
arrayOf("e", reportId),
|
||||
arrayOf("status", "no_longer_there"),
|
||||
arrayOf("expiration", "1701213200"),
|
||||
arrayOf("alt", "Roadstr: event confirmation"),
|
||||
arrayOf("alt", "Roadstr: event denied"),
|
||||
),
|
||||
content = "",
|
||||
sig = "00".repeat(64),
|
||||
@@ -129,7 +129,7 @@ class RoadEventTest {
|
||||
assertEquals(RoadEventStatus.NO_LONGER_THERE, event.status())
|
||||
assertTrue(event.isDenial())
|
||||
assertTrue(!event.isConfirmation())
|
||||
assertEquals("Roadstr: event confirmation", event.alt())
|
||||
assertEquals("Roadstr: event denied", event.alt())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -161,7 +161,7 @@ class RoadEventTest {
|
||||
|
||||
assertEquals(reportId, template.tags.first { it[0] == "e" }[1])
|
||||
assertEquals("still_there", template.tags.first { it[0] == "status" }[1])
|
||||
assertEquals("Roadstr: event confirmation", template.tags.first { it[0] == "alt" }[1])
|
||||
assertEquals("Roadstr: event confirmed", template.tags.first { it[0] == "alt" }[1])
|
||||
assertTrue(template.tags.none { it[0] == "g" || it[0] == "lat" || it[0] == "lon" })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user