mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 08:20:51 +00: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.EmojiPackEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
|
import com.vitorpamplona.quartz.events.FhirResourceEvent
|
||||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||||
import com.vitorpamplona.quartz.events.FileServersEvent
|
import com.vitorpamplona.quartz.events.FileServersEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageEvent
|
import com.vitorpamplona.quartz.events.FileStorageEvent
|
||||||
@ -1363,6 +1364,26 @@ object LocalCache {
|
|||||||
refreshObservers(note)
|
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(
|
fun consume(
|
||||||
event: HighlightEvent,
|
event: HighlightEvent,
|
||||||
relay: Relay?,
|
relay: Relay?,
|
||||||
@ -1981,6 +2002,7 @@ object LocalCache {
|
|||||||
is EmojiPackEvent -> consume(event, relay)
|
is EmojiPackEvent -> consume(event, relay)
|
||||||
is EmojiPackSelectionEvent -> consume(event, relay)
|
is EmojiPackSelectionEvent -> consume(event, relay)
|
||||||
is SealedGossipEvent -> consume(event, relay)
|
is SealedGossipEvent -> consume(event, relay)
|
||||||
|
is FhirResourceEvent -> consume(event, relay)
|
||||||
is FileHeaderEvent -> consume(event, relay)
|
is FileHeaderEvent -> consume(event, relay)
|
||||||
is FileServersEvent -> consume(event, relay)
|
is FileServersEvent -> consume(event, relay)
|
||||||
is FileStorageEvent -> 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.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -53,11 +54,13 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@ -66,6 +69,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Alignment.Companion.Center
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.capitalize
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
@ -94,6 +100,8 @@ import androidx.lifecycle.map
|
|||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil.compose.AsyncImagePainter
|
||||||
import coil.request.SuccessResult
|
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.GeoHash
|
||||||
import com.fonfon.kgeohash.toGeoHash
|
import com.fonfon.kgeohash.toGeoHash
|
||||||
import com.vitorpamplona.amethyst.R
|
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.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
|
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
|
||||||
import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth
|
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.AddButton
|
||||||
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost
|
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost
|
||||||
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost
|
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.EmojiPackSelectionEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiUrl
|
import com.vitorpamplona.quartz.events.EmojiUrl
|
||||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
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.FileHeaderEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
@ -239,8 +250,11 @@ import kotlinx.collections.immutable.persistentListOf
|
|||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.NumberFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -1176,6 +1190,9 @@ private fun RenderNoteRow(
|
|||||||
is BadgeAwardEvent -> {
|
is BadgeAwardEvent -> {
|
||||||
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
|
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
is FhirResourceEvent -> {
|
||||||
|
RenderFhirResource(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
is PeopleListEvent -> {
|
is PeopleListEvent -> {
|
||||||
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
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)
|
Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE)
|
||||||
|
|
||||||
val acceptedNIP19schemes =
|
val acceptedNIP19schemes =
|
||||||
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1") +
|
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1", "nembed") +
|
||||||
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").map {
|
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1", "nembed").map {
|
||||||
it.uppercase()
|
it.uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,38 @@ class NIP19EmbedTests {
|
|||||||
assertEquals(eyeglassesPrescriptionEvent!!.toJson(), decodedNote.toJson())
|
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
|
@Test
|
||||||
fun testCompressionSizes() {
|
fun testCompressionSizes() {
|
||||||
@ -138,5 +170,6 @@ class NIP19EmbedTests {
|
|||||||
assertTrue(true)
|
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)
|
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
FileStorageHeaderEvent.KIND ->
|
FileStorageHeaderEvent.KIND ->
|
||||||
FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
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)
|
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GitIssueEvent.KIND -> GitIssueEvent(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…
Reference in New Issue
Block a user