mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Basic support for FHIR payloads.
Support for nembed
This commit is contained in:
parent
e305f3c198
commit
328ed226c6
@ -63,6 +63,7 @@ import com.vitorpamplona.quartz.events.DeletionEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.FileServersEvent
|
||||
import com.vitorpamplona.quartz.events.FileStorageEvent
|
||||
@ -1363,6 +1364,26 @@ object LocalCache {
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: FhirResourceEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: HighlightEvent,
|
||||
relay: Relay?,
|
||||
@ -1981,6 +2002,7 @@ object LocalCache {
|
||||
is EmojiPackEvent -> consume(event, relay)
|
||||
is EmojiPackSelectionEvent -> consume(event, relay)
|
||||
is SealedGossipEvent -> consume(event, relay)
|
||||
is FhirResourceEvent -> consume(event, relay)
|
||||
is FileHeaderEvent -> consume(event, relay)
|
||||
is FileServersEvent -> consume(event, relay)
|
||||
is FileStorageEvent -> consume(event, relay)
|
||||
|
123
app/src/main/java/com/vitorpamplona/amethyst/ui/note/MiniFhir.kt
Normal file
123
app/src/main/java/com/vitorpamplona/amethyst/ui/note/MiniFhir.kt
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "resourceType",
|
||||
)
|
||||
@JsonSubTypes(
|
||||
JsonSubTypes.Type(value = Practitioner::class, name = "Practitioner"),
|
||||
JsonSubTypes.Type(value = Patient::class, name = "Patient"),
|
||||
JsonSubTypes.Type(value = Bundle::class, name = "Bundle"),
|
||||
JsonSubTypes.Type(value = VisionPrescription::class, name = "VisionPrescription"),
|
||||
)
|
||||
open class Resource(
|
||||
var resourceType: String? = null,
|
||||
var id: String = "",
|
||||
)
|
||||
|
||||
class Practitioner(
|
||||
resourceType: String? = null,
|
||||
id: String = "",
|
||||
var active: Boolean? = null,
|
||||
var name: ArrayList<HumanName> = arrayListOf(),
|
||||
var gender: String? = null,
|
||||
) : Resource(resourceType, id)
|
||||
|
||||
class Patient(
|
||||
resourceType: String? = null,
|
||||
id: String = "",
|
||||
var active: Boolean? = null,
|
||||
var name: ArrayList<HumanName> = arrayListOf(),
|
||||
var gender: String? = null,
|
||||
) : Resource(resourceType, id)
|
||||
|
||||
class HumanName(
|
||||
var use: String? = null,
|
||||
var family: String? = null,
|
||||
var given: ArrayList<String> = arrayListOf(),
|
||||
) {
|
||||
fun assembleName(): String {
|
||||
return given.joinToString(" ") + " " + family
|
||||
}
|
||||
}
|
||||
|
||||
class Bundle(
|
||||
resourceType: String? = null,
|
||||
id: String = "",
|
||||
var type: String? = null,
|
||||
var created: String? = null,
|
||||
var entry: List<Resource> = arrayListOf(),
|
||||
) : Resource(resourceType, id)
|
||||
|
||||
class VisionPrescription(
|
||||
resourceType: String? = null,
|
||||
id: String = "",
|
||||
var status: String? = null,
|
||||
var created: String? = null,
|
||||
var patient: Reference? = Reference(),
|
||||
var encounter: Reference? = Reference(),
|
||||
var dateWritten: String? = null,
|
||||
var prescriber: Reference? = Reference(),
|
||||
var lensSpecification: List<LensSpecification> = arrayListOf(),
|
||||
) : Resource(resourceType, id)
|
||||
|
||||
class LensSpecification(
|
||||
var eye: String? = null,
|
||||
var sphere: Double? = null,
|
||||
var cylinder: Double? = null,
|
||||
var axis: Double? = null,
|
||||
var add: Double? = null,
|
||||
var prism: List<Prism> = arrayListOf(),
|
||||
// contact lenses
|
||||
var power: Double? = null,
|
||||
var backCurve: Double? = null,
|
||||
var diameter: Double? = null,
|
||||
var color: String? = null,
|
||||
var brand: String? = null,
|
||||
var note: String? = null,
|
||||
)
|
||||
|
||||
class Prism(
|
||||
var amount: Double? = null,
|
||||
var base: String? = null,
|
||||
)
|
||||
|
||||
class Reference(
|
||||
var reference: String? = null,
|
||||
)
|
||||
|
||||
fun findReferenceInDb(
|
||||
it: String,
|
||||
db: Map<String, Resource>,
|
||||
): Resource? {
|
||||
val parts = it.split("/")
|
||||
return if (parts.size == 2) {
|
||||
db.get(parts[1].removePrefix("#"))
|
||||
} else {
|
||||
db.get(it.removePrefix("#"))
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@ -53,11 +54,13 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@ -66,6 +69,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.Center
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -81,9 +85,11 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.capitalize
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
@ -94,6 +100,8 @@ import androidx.lifecycle.map
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.AsyncImagePainter
|
||||
import coil.request.SuccessResult
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fonfon.kgeohash.GeoHash
|
||||
import com.fonfon.kgeohash.toGeoHash
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@ -125,6 +133,7 @@ import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
|
||||
import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth
|
||||
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.elements.AddButton
|
||||
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost
|
||||
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost
|
||||
@ -206,6 +215,8 @@ import com.vitorpamplona.quartz.events.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.events.EmojiUrl
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||
@ -239,8 +250,11 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@ -1176,6 +1190,9 @@ private fun RenderNoteRow(
|
||||
is BadgeAwardEvent -> {
|
||||
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
is FhirResourceEvent -> {
|
||||
RenderFhirResource(baseNote, accountViewModel, nav)
|
||||
}
|
||||
is PeopleListEvent -> {
|
||||
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
@ -4346,3 +4363,223 @@ fun CreateImageHeader(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderEyeGlassesPrescriptionPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
val nav: (String) -> Unit = {}
|
||||
|
||||
val prescriptionEvent = Event.fromJson("{\"id\":\"0c15d2bc6f7dcc42fa4426d35d30d09840c9afa5b46d100415006e41d6471416\",\"pubkey\":\"bcd4715cc34f98dce7b52fddaf1d826e5ce0263479b7e110a5bd3c3789486ca8\",\"created_at\":1709074097,\"kind\":82,\"tags\":[],\"content\":\"{\\\"resourceType\\\":\\\"Bundle\\\",\\\"id\\\":\\\"bundle-vision-test\\\",\\\"type\\\":\\\"document\\\",\\\"entry\\\":[{\\\"resourceType\\\":\\\"Practitioner\\\",\\\"id\\\":\\\"2\\\",\\\"active\\\":true,\\\"name\\\":[{\\\"use\\\":\\\"official\\\",\\\"family\\\":\\\"Careful\\\",\\\"given\\\":[\\\"Adam\\\"]}],\\\"gender\\\":\\\"male\\\"},{\\\"resourceType\\\":\\\"Patient\\\",\\\"id\\\":\\\"1\\\",\\\"active\\\":true,\\\"name\\\":[{\\\"use\\\":\\\"official\\\",\\\"family\\\":\\\"Duck\\\",\\\"given\\\":[\\\"Donald\\\"]}],\\\"gender\\\":\\\"male\\\"},{\\\"resourceType\\\":\\\"VisionPrescription\\\",\\\"status\\\":\\\"active\\\",\\\"created\\\":\\\"2014-06-15\\\",\\\"patient\\\":{\\\"reference\\\":\\\"#1\\\"},\\\"dateWritten\\\":\\\"2014-06-15\\\",\\\"prescriber\\\":{\\\"reference\\\":\\\"#2\\\"},\\\"lensSpecification\\\":[{\\\"eye\\\":\\\"right\\\",\\\"sphere\\\":-2,\\\"prism\\\":[{\\\"amount\\\":0.5,\\\"base\\\":\\\"down\\\"}],\\\"add\\\":2},{\\\"eye\\\":\\\"left\\\",\\\"sphere\\\":-1,\\\"cylinder\\\":-0.5,\\\"axis\\\":180,\\\"prism\\\":[{\\\"amount\\\":0.5,\\\"base\\\":\\\"up\\\"}],\\\"add\\\":2}]}]}\",\"sig\":\"dc58f6109111ca06920c0c711aeaf8e2ee84975afa60d939828d4e01e2edea738f735fb5b1fcadf6d5496e36ac429abf7020a55fd1e4ed215738afc8d07cb950\"}") as FhirResourceEvent
|
||||
|
||||
RenderFhirResource(prescriptionEvent, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderFhirResource(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val event = baseNote.event as? FhirResourceEvent ?: return
|
||||
|
||||
RenderFhirResource(event, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderFhirResource(
|
||||
event: FhirResourceEvent,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
var baseResource: Resource? by remember(event) {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = event) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val mapper =
|
||||
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
|
||||
try {
|
||||
baseResource = mapper.readValue(event.content, Resource::class.java)
|
||||
} catch (e: Exception) {
|
||||
Log.e("RenderEyeGlassesPrescription", "Parser error", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseResource?.let { resource ->
|
||||
when (resource) {
|
||||
is Bundle -> {
|
||||
val db = resource.entry.associate { it.id to it }
|
||||
val vision = resource.entry.filterIsInstance(VisionPrescription::class.java)
|
||||
|
||||
vision.firstOrNull()?.let {
|
||||
RenderEyeGlassesPrescription(it, db, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
is VisionPrescription -> {
|
||||
val db = mapOf(resource.id to resource)
|
||||
RenderEyeGlassesPrescription(resource, db, accountViewModel, nav)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderEyeGlassesPrescription(
|
||||
visionPrescription: VisionPrescription,
|
||||
db: Map<String, Resource>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(Size10dp),
|
||||
) {
|
||||
val rightEye = visionPrescription.lensSpecification.firstOrNull { it.eye == "right" }
|
||||
val leftEye = visionPrescription.lensSpecification.firstOrNull { it.eye == "left" }
|
||||
|
||||
Text(
|
||||
"Eyeglasses Prescription",
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
Spacer(StdVertSpacer)
|
||||
|
||||
visionPrescription.patient?.reference?.let {
|
||||
val patient = findReferenceInDb(it, db) as? Patient
|
||||
|
||||
patient?.name?.firstOrNull()?.assembleName()?.let {
|
||||
Text(
|
||||
text = "Patient: $it",
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
visionPrescription.status?.let {
|
||||
Text(
|
||||
text = "Status: ${it.capitalize()}",
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(DoubleVertSpacer)
|
||||
|
||||
RenderEyeGlassesPrescriptionHeaderRow()
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
|
||||
rightEye?.let {
|
||||
RenderEyeGlassesPrescriptionRow(data = it)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
|
||||
leftEye?.let {
|
||||
RenderEyeGlassesPrescriptionRow(data = it)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
|
||||
Spacer(DoubleVertSpacer)
|
||||
|
||||
visionPrescription.prescriber?.reference?.let {
|
||||
val practitioner = findReferenceInDb(it, db) as? Practitioner
|
||||
|
||||
practitioner?.name?.firstOrNull()?.assembleName()?.let {
|
||||
Text(
|
||||
text = "Signed by: $it",
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth(),
|
||||
textAlign = TextAlign.Right,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderEyeGlassesPrescriptionHeaderRow() {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = "Eye",
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
VerticalDivider(thickness = DividerThickness)
|
||||
Text(
|
||||
text = "Sph",
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = "Cyl",
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = "Axis",
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
VerticalDivider(thickness = DividerThickness)
|
||||
Text(
|
||||
text = "Add",
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderEyeGlassesPrescriptionRow(data: LensSpecification) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val numberFormat = DecimalFormat("##.00")
|
||||
val integerFormat = DecimalFormat("###")
|
||||
|
||||
Text(
|
||||
text = data.eye?.capitalize() ?: "Unknown",
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
VerticalDivider(thickness = DividerThickness)
|
||||
Text(
|
||||
text = formatOrBlank(data.sphere, numberFormat),
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = formatOrBlank(data.cylinder, numberFormat),
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = formatOrBlank(data.axis, integerFormat),
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
VerticalDivider(thickness = DividerThickness)
|
||||
Text(
|
||||
text = formatOrBlank(data.add, numberFormat),
|
||||
textAlign = TextAlign.Right,
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun formatOrBlank(
|
||||
amount: Double?,
|
||||
numberFormat: NumberFormat,
|
||||
): String {
|
||||
if (amount == null) return ""
|
||||
if (Math.abs(amount) < 0.01) return ""
|
||||
return numberFormat.format(amount)
|
||||
}
|
||||
|
@ -319,8 +319,8 @@ class RichTextParser() {
|
||||
Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE)
|
||||
|
||||
val acceptedNIP19schemes =
|
||||
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1") +
|
||||
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").map {
|
||||
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1", "nembed") +
|
||||
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1", "nembed").map {
|
||||
it.uppercase()
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,38 @@ class NIP19EmbedTests {
|
||||
assertEquals(eyeglassesPrescriptionEvent!!.toJson(), decodedNote.toJson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVisionPrescriptionBundleEmbedEvent() {
|
||||
val signer =
|
||||
NostrSignerInternal(
|
||||
KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")),
|
||||
)
|
||||
|
||||
var eyeglassesPrescriptionEvent: Event? = null
|
||||
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
|
||||
FhirResourceEvent.create(fhirPayload = visionPrescriptionBundle, signer = signer) {
|
||||
eyeglassesPrescriptionEvent = it
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
|
||||
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
|
||||
|
||||
assertNotNull(eyeglassesPrescriptionEvent)
|
||||
|
||||
val bech32 = Nip19Bech32.createNEmbed(eyeglassesPrescriptionEvent!!)
|
||||
|
||||
println(eyeglassesPrescriptionEvent!!.toJson())
|
||||
println(bech32)
|
||||
|
||||
val decodedNote = (Nip19Bech32.uriToRoute(bech32)?.entity as Nip19Bech32.NEmbed).event
|
||||
|
||||
assertTrue(decodedNote.hasValidSignature())
|
||||
|
||||
assertEquals(eyeglassesPrescriptionEvent!!.toJson(), decodedNote.toJson())
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
fun testCompressionSizes() {
|
||||
@ -138,5 +170,6 @@ class NIP19EmbedTests {
|
||||
assertTrue(true)
|
||||
}*/
|
||||
|
||||
val visionPrescriptionFhir = "{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"Patient/example\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"Practitioner/example\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}"
|
||||
val visionPrescriptionFhir = "{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"Patient/Donald Duck\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"Practitioner/Adam Careful\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}"
|
||||
val visionPrescriptionBundle = "{\"resourceType\":\"Bundle\",\"id\":\"bundle-vision-test\",\"type\":\"document\",\"entry\":[{\"resourceType\":\"Practitioner\",\"id\":\"2\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Careful\",\"given\":[\"Adam\"]}],\"gender\":\"male\"},{\"resourceType\":\"Patient\",\"id\":\"1\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Duck\",\"given\":[\"Donald\"]}],\"gender\":\"male\"},{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"#1\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"#2\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}]}"
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ class EventFactory {
|
||||
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageHeaderEvent.KIND ->
|
||||
FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class FhirResourceEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
companion object {
|
||||
const val KIND = 82
|
||||
|
||||
fun create(
|
||||
fhirPayload: String,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (FhirResourceEvent) -> Unit,
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), fhirPayload, onReady)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user