Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce S4H PDF upload #444

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions .github/workflows/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ jobs:
- run: flutter pub get
- run: flutter pub run build_runner build
- run: flutter build apk
env:
GPR_USER: ${{ secrets.GPR_USER }}
GPR_TOKEN: ${{ secrets.GPR_TOKEN }}
D4L_CLIENT_ID: ${{ secrets.D4L_CLIENT_ID }}
D4L_CLIENT_SECRET: ${{ secrets.D4L_CLIENT_SECRET }}

build-ios:
name: Build iOS Package
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
*.log
.idea/
21 changes: 21 additions & 0 deletions app/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ Please also see the [contribution guide in the root folder](../CONTRIBUTING.md).
You should now be able to run the app by opening the debug panel on the left and
pressing the green triangle at the top (or using the shortcut <kbd>F5</kbd>).

### `secrets.properties`

The CHDP libraries require a few secrets to build. In
[`android/secrets.properties`](android/secrets.properties) (gitignored) add the
following keys and appropriate values:

```properties
gpr.user=
gpr.token=

d4l.clientId=
d4l.clientSecret=
```

The first two are a Github username and Personal Access Token with the
`read:packages` scope. This is used by gradle to download the SDK from D4L's
private repositories.

The next two are secrets given by D4L. Ask Thomas at Thomas.Harris (at) hpi.de
for them.

## Architecture

The app consists of multiple so-called modules. Our main modules correspond to
Expand Down
1 change: 1 addition & 0 deletions app/android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java
key.properties
**/*.keystore
**/*.jks
secrets.properties
35 changes: 34 additions & 1 deletion app/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

def secrets = new Properties()
def secretsPropertiesFile = new File(rootProject.projectDir, "secrets.properties")
if (secretsPropertiesFile.isFile()) {
secretsPropertiesFile.withReader("UTF-8") { reader -> secrets.load(reader) }
} else {
assert System.getenv('GPR_USER')
assert System.getenv('GPR_TOKEN')
assert System.getenv('D4L_CLIENT_ID')
assert System.getenv('D4L_CLIENT_SECRET')
secrets.setProperty("gpr.user", System.getenv('GPR_USER'))
secrets.setProperty("gpr.token", System.getenv('GPR_TOKEN'))
secrets.setProperty("d4l.clientId", System.getenv('D4L_CLIENT_ID'))
secrets.setProperty("d4l.clientSecret", System.getenv('D4L_CLIENT_SECRET'))
}

android {
compileSdkVersion 31

Expand All @@ -44,13 +59,27 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "de.hpi.pharme"
minSdkVersion 19
minSdkVersion 23
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

manifestPlaceholders += [
platform : "s4h",
environment : "staging",
clientId : secrets.getProperty("d4l.clientId"),
clientSecret : secrets.getProperty("d4l.clientSecret"),
redirectScheme: "eu.smart4health.7cf2befc-a010-4290-996c-a94426b5ce65",
debug : "false"
]
}

buildTypes {

debug {
matchingFallbacks = ["release", "debug"]
}

release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
Expand All @@ -65,4 +94,8 @@ flutter {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

implementation "care.data4life.hc-sdk-kmp:sdk-android:1.15.1"
implementation "care.data4life.hc-fhir-sdk-java:fhir-java:1.6.2"
implementation "com.jakewharton.threetenabp:threetenabp:1.4.0"
}
7 changes: 0 additions & 7 deletions app/android/app/src/debug/AndroidManifest.xml

This file was deleted.

2 changes: 2 additions & 0 deletions app/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
<application
android:label="PharMe"
android:icon="@mipmap/launcher_icon"
android:theme="@style/NormalTheme"
android:usesCleartextTraffic="false"
android:name="${applicationName}"
>
<activity
android:name=".MainActivity"
Expand Down
147 changes: 146 additions & 1 deletion app/android/app/src/main/kotlin/de/hpi/frasecys/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,151 @@
package de.hpi.pharme

import io.flutter.embedding.android.FlutterActivity
import com.jakewharton.threetenabp.AndroidThreeTen
import care.data4life.sdk.Data4LifeClient
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.os.Bundle
import care.data4life.sdk.listener.ResultListener
import care.data4life.sdk.lang.D4LException
import care.data4life.fhir.r4.model.Attachment
import care.data4life.fhir.r4.model.CodeSystemDocumentReferenceStatus
import care.data4life.fhir.r4.model.DocumentReference
import android.util.Base64
import java.io.File
import java.security.MessageDigest
import care.data4life.sdk.call.Fhir4Record
import java.util.Calendar
import care.data4life.fhir.r4.util.FhirDateTimeConverter

class MainActivity: FlutterActivity() {
class MainActivity : FlutterActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

AndroidThreeTen.init(this.applicationContext)
Data4LifeClient.init(applicationContext)
}


override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "chdp")
.setMethodCallHandler { call, result ->
when (call.method) {
"isLoggedIn" -> Data4LifeClient.getInstance().isUserLoggedIn(
object : ResultListener<Boolean> {
override fun onSuccess(t: Boolean) = result.success(t)

override fun onError(exception: D4LException) =
result.error(
"D4LException",
"Failed to check login status",
exception
)
}
)
"login" -> {
val intent = Data4LifeClient.getInstance().getLoginIntent(
this@MainActivity,
setOf(
"perm:r",
"rec:r",
"rec:w",
"attachment:r",
"attachment:w",
"user:r",
"user:w",
"user:q",
)
)

[email protected](intent, Data4LifeClient.D4L_AUTH)

result.success(0)
}
"logout" -> {
Data4LifeClient.getInstance().logout(
object : care.data4life.sdk.listener.Callback {
override fun onSuccess() = result.success(0)

override fun onError(exception: D4LException) =
result.error(
"D4LException",
"Failed to logout",
exception
)
}
)

}
"upload" -> {
val path: String = call.argument("path")!!
val title: String = call.argument("title")!!
val docRef = makeDocumentReference(path, title)

Data4LifeClient.getInstance().fhir4.create(
resource = docRef,
annotations = listOf("pharme"),
callback = object :
care.data4life.sdk.call.Callback<Fhir4Record<DocumentReference>> {
override fun onSuccess(_result: Fhir4Record<DocumentReference>) {
result.success(true)
}

override fun onError(exception: D4LException) {
result.success(false)
}
}
)
}
"toast" -> {
val msg: String = call.argument("msg")!!

android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show()
}
else -> result.notImplemented()
}
}
}

override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: android.content.Intent?,
) {
super.onActivityResult(requestCode, resultCode, data)

if (requestCode == Data4LifeClient.D4L_AUTH) {
if (resultCode == android.app.Activity.RESULT_OK) {
android.util.Log.i("PHARME", "login successful")
}
}
}
}

fun makeDocumentReference(path: String, title: String): DocumentReference {
val bytes = File(path).readBytes()
return DocumentReference(
CodeSystemDocumentReferenceStatus.CURRENT,
listOf(
DocumentReference.DocumentReferenceContent(
Attachment().apply {
contentType = "application/pdf"
data = Base64.encodeToString(
bytes,
Base64.NO_WRAP,
)
size = bytes.size
hash = with(MessageDigest.getInstance("SHA-1")) {
Base64.encodeToString(digest(bytes), Base64.NO_WRAP)
}
},
),
),
).apply {
description = title
date = FhirDateTimeConverter.toFhirInstant(Calendar.getInstance().time)
}
}
4 changes: 2 additions & 2 deletions app/android/app/src/main/res/values-night/styles.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="LaunchTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
Expand All @@ -12,7 +12,7 @@
running.

This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="NormalTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
4 changes: 2 additions & 2 deletions app/android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
Expand All @@ -12,7 +12,7 @@
running.

This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="NormalTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
40 changes: 40 additions & 0 deletions app/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,50 @@ buildscript {
}
}

def secrets = new Properties()
def secretsPropertiesFile = new File(rootProject.projectDir, "secrets.properties")
if (secretsPropertiesFile.isFile()) {
secretsPropertiesFile.withReader("UTF-8") { reader -> secrets.load(reader) }
} else {
assert System.getenv('GPR_USER')
assert System.getenv('GPR_TOKEN')
assert System.getenv('D4L_CLIENT_ID')
assert System.getenv('D4L_CLIENT_SECRET')
secrets.setProperty("gpr.user", System.getenv('GPR_USER'))
secrets.setProperty("gpr.token", System.getenv('GPR_TOKEN'))
secrets.setProperty("d4l.clientId", System.getenv('D4L_CLIENT_ID'))
secrets.setProperty("d4l.clientSecret", System.getenv('D4L_CLIENT_SECRET'))
}

allprojects {
repositories {
google()
mavenCentral()

// tried to loop over a list of urls like in kotlin but no dice
maven {
url "https://maven.pkg.github.com/d4l-data4life/hc-util-sdk-kmp"
credentials {
username secrets.getProperty("gpr.user")
password secrets.getProperty("gpr.token")
}
}

maven {
url "https://maven.pkg.github.com/d4l-data4life/hc-fhir-sdk-java"
credentials {
username secrets.getProperty("gpr.user")
password secrets.getProperty("gpr.token")
}
}

maven {
url "https://maven.pkg.github.com/d4l-data4life/hc-fhir-helper-sdk-kmp"
credentials {
username secrets.getProperty("gpr.user")
password secrets.getProperty("gpr.token")
}
}
}
}

Expand Down
6 changes: 2 additions & 4 deletions app/lib/common/models/medication/guideline.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';

import '../../module.dart';

part 'guideline.g.dart';

@HiveType(typeId: 9)
Expand Down Expand Up @@ -70,7 +68,7 @@ class Guideline {

@override
int get hashCode {
return hashValues(
return Object.hash(
implication,
recommendation,
warningLevel,
Expand Down Expand Up @@ -115,7 +113,7 @@ class Phenotype {
}

@override
int get hashCode => hashValues(geneResult.name, geneSymbol.name);
int get hashCode => Object.hash(geneResult.name, geneSymbol.name);
}

@HiveType(typeId: 11)
Expand Down
Loading