androidExposureCopy = new ConcurrentHashMap<>(androidExposures);
for (String encodedRpi : androidExposureCopy.keySet()) {
removeAndroidRpi(encodedRpi);
}
diff --git a/android/app/src/main/java/edu/illinois/covid/exposure/ble/ExposureClient.java b/android/app/src/main/java/edu/illinois/covid/exposure/ble/ExposureClient.java
index 3310ae50..99035632 100644
--- a/android/app/src/main/java/edu/illinois/covid/exposure/ble/ExposureClient.java
+++ b/android/app/src/main/java/edu/illinois/covid/exposure/ble/ExposureClient.java
@@ -106,6 +106,11 @@ public void onCreate() {
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand - " + startId);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // This call is required to be made after startForegroundService() since API 26
+ startForegroundClientService();
+ }
+
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
diff --git a/android/app/src/main/java/edu/illinois/covid/exposure/ble/NotificationCreator.java b/android/app/src/main/java/edu/illinois/covid/exposure/ble/NotificationCreator.java
index 703f7382..d845ec4d 100644
--- a/android/app/src/main/java/edu/illinois/covid/exposure/ble/NotificationCreator.java
+++ b/android/app/src/main/java/edu/illinois/covid/exposure/ble/NotificationCreator.java
@@ -28,6 +28,7 @@ static Notification getNotification(Context context) {
.setContentTitle(context.getString(R.string.exposure_notification_title))
.setSmallIcon(R.drawable.app_icon)
.setContentIntent(pendingIntent)
+ .setOnlyAlertOnce(true)
.setTicker(context.getString(R.string.exposure_notification_ticker));
notification = builder.build();
diff --git a/android/build.gradle b/android/build.gradle
index 72a3775c..96d73503 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -38,7 +38,6 @@ allprojects {
flatDir {
dirs '../lib'
}
- maven { url 'https://maven.microblink.com' }
maven { url 'https://jitpack.io' }
}
}
diff --git a/assets/flexUI.json b/assets/flexUI.json
index cec2ab5f..d869212e 100644
--- a/assets/flexUI.json
+++ b/assets/flexUI.json
@@ -7,11 +7,11 @@
"home": ["connect", "stay_healthy", "your_health"],
"home.connect": ["netid", "phone"],
- "home.stay_healthy" : ["recent_event", "next_step", "symptom_checkin", "add_test_result"],
+ "home.stay_healthy" : ["vaccination", "recent_event", "next_step", "symptom_checkin", "add_test_result"],
"home.your_health" : [ "health_status", "tiles", "health_history", "find_test_location", "wellness_center", "_groups", "switch_account"],
"home.your_health.tiles": ["county_guidelines", "care_team"],
- "settings": ["user_info", "connect", "customizations", "connected", "notifications", "covid19", "privacy", "account", "feedback"],
+ "settings": ["user_info", "connect", "customizations", "connected", "notifications", "covid19", "privacy", "account", "get_help"],
"settings.connect": ["netid", "phone"],
"settings.customizations": ["roles"],
"settings.connected": ["netid", "phone"],
diff --git a/assets/health.rules.json b/assets/health.rules.json
index dee6683d..f26e2176 100644
--- a/assets/health.rules.json
+++ b/assets/health.rules.json
@@ -7,8 +7,8 @@
},
"intervals": {
- "DefaultTestMonitorInterval": 8,
- "UndergraduateTestMonitorInterval": 8,
+ "DefaultTestMonitorInterval": 4,
+ "UndergraduateTestMonitorInterval": 2,
"UserTestMonitorInterval": null,
"TestMonitorInterval": {
@@ -19,7 +19,24 @@
"success": "UndergraduateTestMonitorInterval",
"fail": "DefaultTestMonitorInterval"
}
- }
+ },
+
+ "DefaultTestMonitorWeekdaysExtent": ["Fri", "Sat", "Sun", "Mon"],
+ "UndergraduateTestMonitorWeekdaysExtent": ["Sat", "Sun"],
+
+ "TestMonitorWeekdaysExtent": {
+ "condition": "test-user", "params": { "card.role": "Undergraduate", "card.student_level": "1U" },
+ "success": "UndergraduateTestMonitorWeekdaysExtent",
+ "fail": "DefaultTestMonitorWeekdaysExtent"
+ },
+
+ "Mon": 1,
+ "Tue": 2,
+ "Wed": 3,
+ "Thu": 4,
+ "Fri": 5,
+ "Sat": 6,
+ "Sun": 7
},
"constants": {
@@ -82,27 +99,38 @@
"interval": { "scope": "past" },
"vaccine": "effective"
},
- "success": null,
- "fail": {
- "condition": "require-test",
+ "success": {
+ "condition": "test-interval",
"params": {
- "interval": { "min": 11, "max": null, "scope": "future" }
+ "interval": "UserTestMonitorInterval"
},
- "success": null,
- "fail": {
- "code": "red",
- "priority": 11,
- "next_step": "test.now.step"
- }
- }
+ "success": "PCR.positive.finish",
+ "fail": null
+ },
+ "fail": "PCR.positive.finish"
},
+ "fail": "PCR.positive.quarantine"
+ },
+
+ "PCR.positive.finish": {
+ "condition": "require-test",
+ "params": {
+ "interval": { "min": 11, "max": null, "scope": "future" }
+ },
+ "success": null,
"fail": {
"code": "red",
"priority": 11,
- "next_step_html": "positive.step.html"
+ "next_step": "test.now.step"
}
},
-
+
+ "PCR.positive.quarantine": {
+ "code": "red",
+ "priority": 11,
+ "next_step_html": "positive.step.html"
+ },
+
"PCR.positive-IP": {
"condition": "timeout",
"params": {
@@ -114,30 +142,41 @@
"interval": { "scope": "past" },
"vaccine": "effective"
},
- "success": null,
- "fail": {
- "condition": "require-test",
+ "success": {
+ "condition": "test-interval",
"params": {
- "interval": { "min": 9, "max": null, "scope": "future" }
+ "interval": "UserTestMonitorInterval"
},
- "success": null,
- "fail": {
- "code": "red",
- "priority": 11,
- "next_step": "test.now.step",
- "fcm_topic": "PCR.positive-IP"
- }
- }
+ "success": "PCR.positive-IP.finish",
+ "fail": null
+ },
+ "fail": "PCR.positive-IP.finish"
},
+ "fail": "PCR.positive-IP.quarantine"
+ },
+
+ "PCR.positive-IP.finish": {
+ "condition": "require-test",
+ "params": {
+ "interval": { "min": 9, "max": null, "scope": "future" }
+ },
+ "success": null,
"fail": {
"code": "red",
"priority": 11,
- "next_step_html": "positive-ip.step.html",
- "event_explanation": "positive-ip.explanation",
+ "next_step": "test.now.step",
"fcm_topic": "PCR.positive-IP"
}
},
+ "PCR.positive-IP.quarantine": {
+ "code": "red",
+ "priority": 11,
+ "next_step_html": "positive-ip.step.html",
+ "event_explanation": "positive-ip.explanation",
+ "fcm_topic": "PCR.positive-IP"
+ },
+
"PCR.positive-NIP.0": {
"condition": "timeout",
"params": {
@@ -190,19 +229,70 @@
"test-monitor": {
"condition": "require-test",
"params": {
- "interval": { "min": 0, "max": "TestMonitorInterval", "scope": "future", "current": true }
+ "interval": { "min": 0, "max": "TestMonitorInterval", "max-weekdays-extent": "TestMonitorWeekdaysExtent", "scope": "future", "current": true }
+ },
+ "success": "test-fulfilled",
+ "fail": "test-required"
+ },
+
+ "test-fulfilled": {
+ "condition": "require-vaccine",
+ "params": {
+ "interval": { "scope": "past" },
+ "vaccine": "effective"
},
"success": {
- "code": "yellow",
- "priority": 1,
- "next_step_html": "test.monitor.step.html",
- "next_step_interval": "TestMonitorInterval",
- "warning": "test.future.warning"
+ "condition": "test-interval",
+ "params": {
+ "interval": "UserTestMonitorInterval"
+ },
+ "success": "test-fulfilled-vaccinated",
+ "fail": "test-fulfilled-default"
},
- "fail": "test-required"
+ "fail": "test-fulfilled-default"
+ },
+
+ "test-fulfilled-vaccinated": {
+ "code": "yellow",
+ "priority": 1,
+ "next_step_html": "test.monitor.vaccinated.step.html",
+ "next_step_interval": "TestMonitorInterval",
+ "warning": "test.future.warning"
+ },
+
+ "test-fulfilled-default": {
+ "code": "yellow",
+ "priority": 1,
+ "next_step_html": "test.monitor.step.html",
+ "next_step_interval": "TestMonitorInterval",
+ "warning": "test.future.warning"
},
"test-required": {
+ "condition": "require-vaccine",
+ "params": {
+ "interval": { "scope": "past" },
+ "vaccine": "effective"
+ },
+ "success": {
+ "condition": "test-interval",
+ "params": {
+ "interval": "UserTestMonitorInterval"
+ },
+ "success": "test-required-vaccinated",
+ "fail": "test-required-default"
+ },
+ "fail": "test-required-default"
+ },
+
+ "test-required-vaccinated": {
+ "code": "orange",
+ "priority": 1,
+ "next_step_html": "test.now.vaccinated.step.html",
+ "reason": "test.now.reason"
+ },
+
+ "test-required-default": {
"code": "orange",
"priority": 1,
"next_step": "test.now.step",
@@ -238,14 +328,41 @@
}
},
+ "vaccinated-handler": {
+ "condition": "test-interval",
+ "params": {
+ "interval": "UserTestMonitorInterval"
+ },
+ "success": "vaccinated-suspended",
+ "fail": "vaccinated"
+ },
+
"vaccinated": {
"code": "green",
"priority": 4,
"next_step_html": "vaccinated.step.html",
+ "notice": "vaccinated.notice",
"reason": "vaccinated.reason",
"fcm_topic": "vaccinated"
},
+ "vaccinated-force": {
+ "code": "green",
+ "priority": -4,
+ "next_step_html": "vaccinated.step.html",
+ "notice": "vaccinated.notice",
+ "reason": "vaccinated.reason",
+ "fcm_topic": "vaccinated"
+ },
+
+ "vaccinated-suspended": {
+ "code": null,
+ "priority": null,
+ "next_step_html": "test.now.vaccinated.step.html",
+ "notice": "vaccinated.suspended.notice",
+ "reason": "vaccinated.suspended.reason"
+ },
+
"quarantine-on": {
"code": "orange",
"priority": 10,
@@ -261,30 +378,33 @@
"vaccine": "effective"
},
"success": {
- "code": "green",
- "priority": -4,
- "next_step_html": "vaccinated.step.html",
- "reason": "vaccinated.reason",
- "fcm_topic": "vaccinated"
- },
- "fail": {
- "condition": "require-test",
+ "condition": "test-interval",
"params": {
- "interval": { "min": 0, "max": "TestMonitorInterval", "scope": "future", "current": true }
+ "interval": "UserTestMonitorInterval"
},
- "success": {
- "code": "yellow",
- "priority": -1,
- "next_step": "test.resume.step",
- "next_step_interval": "TestMonitorInterval",
- "warning": "test.future.warning"
- },
- "fail": {
- "code": "orange",
- "priority": -1,
- "next_step": "test.now.step",
- "reason": "test.now.reason"
- }
+ "success": "quarantine-off.unvaccinated",
+ "fail": "vaccinated-force"
+ },
+ "fail": "quarantine-off.unvaccinated"
+ },
+
+ "quarantine-off.unvaccinated": {
+ "condition": "require-test",
+ "params": {
+ "interval": { "min": 0, "max": "TestMonitorInterval", "scope": "future", "current": true }
+ },
+ "success": {
+ "code": "yellow",
+ "priority": -1,
+ "next_step": "test.resume.step",
+ "next_step_interval": "TestMonitorInterval",
+ "warning": "test.future.warning"
+ },
+ "fail": {
+ "code": "orange",
+ "priority": -1,
+ "next_step": "test.now.step",
+ "reason": "test.now.reason"
}
},
@@ -332,14 +452,41 @@
"params": {
"interval": { "min": -1, "max": 1, "current": true }
},
+ "success": "out-of-test-compliance-fulfilled",
+ "fail": "test-required"
+ },
+
+ "out-of-test-compliance-fulfilled": {
+ "condition": "require-vaccine",
+ "params": {
+ "interval": { "scope": "past" },
+ "vaccine": "effective"
+ },
"success": {
- "code": null,
- "priority": 1,
- "next_step_html": "test.monitor.step.html",
- "next_step_interval": 1,
- "warning": "test.future.warning"
+ "condition": "test-interval",
+ "params": {
+ "interval": "UserTestMonitorInterval"
+ },
+ "success": "out-of-test-compliance-fulfilled-vaccinated",
+ "fail": "out-of-test-compliance-fulfilled-default"
},
- "fail": "test-required"
+ "fail": "out-of-test-compliance-fulfilled-default"
+ },
+
+ "out-of-test-compliance-fulfilled-vaccinated": {
+ "code": null,
+ "priority": 1,
+ "next_step_html": "test.monitor.vaccinated.step.html",
+ "next_step_interval": 1,
+ "warning": "test.future.warning"
+ },
+
+ "out-of-test-compliance-fulfilled-default": {
+ "code": null,
+ "priority": 1,
+ "next_step_html": "test.monitor.step.html",
+ "next_step_interval": 1,
+ "warning": "test.future.warning"
},
"exempt-on": {
@@ -352,15 +499,7 @@
"params": {
"interval": { "min": 0, "max": "ExemptInterval", "scope": "future" }
},
- "success": {
- "condition": "require-vaccine",
- "params": {
- "interval": { "scope": "past" },
- "vaccine": "effective"
- },
- "success": "vaccinated",
- "fail": "test-required"
- },
+ "success": null,
"fail": {
"code": "yellow",
"priority": 12,
@@ -386,20 +525,23 @@
"vaccine": "effective"
},
"success": {
- "code": "green",
- "priority": -4,
- "next_step_html": "vaccinated.step.html",
- "reason": "exempt-off.green.reason",
- "fcm_topic": "vaccinated"
- },
- "fail": {
- "code": "orange",
- "priority": -1,
- "next_step": "test.now.step",
- "reason": "exempt-off.orange.reason"
- }
+ "condition": "test-interval",
+ "params": {
+ "interval": "UserTestMonitorInterval"
+ },
+ "success": "exempt-off.unvaccinated",
+ "fail": "vaccinated-force"
+ },
+ "fail": "exempt-off.unvaccinated"
},
-
+
+ "exempt-off.unvaccinated": {
+ "code": "orange",
+ "priority": -1,
+ "next_step": "test.now.step",
+ "reason": "exempt-off.orange.reason"
+ },
+
"release": {
"condition": "require-vaccine",
"params": {
@@ -407,21 +549,24 @@
"vaccine": "effective"
},
"success": {
- "code": "green",
- "priority": -4,
- "next_step_html": "vaccinated.step.html",
- "reason": "vaccinated.reason",
- "fcm_topic": "vaccinated"
+ "condition": "test-interval",
+ "params": {
+ "interval": "UserTestMonitorInterval"
+ },
+ "success": "release.unvaccinated",
+ "fail": "vaccinated-force"
},
- "fail": {
- "code": "orange",
- "priority": -1,
- "next_step": "release.step",
- "reason": "release.reason"
- }
+ "fail": "release.unvaccinated"
}
},
+ "release.unvaccinated": {
+ "code": "orange",
+ "priority": -1,
+ "next_step": "release.step",
+ "reason": "release.reason"
+ },
+
"tests" : {
"rules": [
{
@@ -807,7 +952,7 @@
"rules": [
{
"vaccine": "effective",
- "status": "vaccinated"
+ "status": "vaccinated-handler"
}
]
},
@@ -821,8 +966,10 @@
"positive-nip.step.html": "You are not required to self-isolate unless you have COVID-like symptoms and are directed to do so by your licensed health professional.
",
"positive-nip.explanation": "Your Saliva PCR test shows the VIRUS IS DETECTED in your REPEAT POSITIVE at a NON-INFECTIOUS LEVEL.",
"test.monitor.step.html": "Monitor your test results
The university encourages you to be vaccinated if you are able to do so. Visit vaccinefinder.org to find nearby appointments.
",
+ "test.monitor.vaccinated.step.html": "Monitor your test results
We have a verified record of your completed COVID-19 vaccination on file.
You have been identified as being in an area with a significant increase in positive COVID-19 cases over a short period of time. You are required to receive an on-campus COVID-19 test to maintain compliance and have “Granted” Building Access status until cases improve and you are notified otherwise.
",
"test.now.step": "Get a test now",
"test.now.reason": "Your status changed to Orange because you are past due for a test.",
+ "test.now.vaccinated.step.html": "Get a test now.
You have been identified as being in an area with a significant increase in positive COVID-19 cases over a short period of time. Starting now, you are required to receive an on-campus COVID-19 test every other day (even if you are fully vaccinated and your vaccination record has been verified) to maintain compliance and have “Granted” Building Access status until cases improve and you are notified otherwise.
",
"test.another.asap.step": "Get another test asap",
"test.another.now.step.html": "Get your second test now.
- Limit yourself to essential activities until you get the second negative result.
- Your building access will change to Granted (Yellow) with the second negative test result.
See testing schedule and rules.
",
"test.after.step.html": "Get your second test after {next_step_date}. You must take two on-campus tests by Jan. 25.
- Separate the tests by three days of quarantine: if the first test is on day one, the second test will be on day five.
- Limit yourself to essential activities until you get the second negative result.
- Your building access will change to Granted (Yellow) with the second negative test result.
See testing schedule and rules.
",
@@ -837,7 +984,10 @@
"exposure.step.html": "You have likely been exposed to a person who is infected with COVID-19.
- You must quarantine yourself immediately.
- Stay home. Do not go to work, school, or public areas.
- Separate yourself from others in your home.
- Contact covidwellness@illinois.edu for guidance.
- Get tested after {next_step_date} to see if you have developed the disease.
- More Info: Quarantine and Isolation
",
"exposure.reason": "Your status changed to Orange because you received an exposure notification.",
"vaccinated.step.html": "We have a verified record of your completed COVID-19 vaccination on file.
Your vaccination status replaces testing for compliance and building access until further notice.
Please get an on-campus COVID-19 test if you experience symptoms.
Continue to monitor university communications for any changes to your testing policy.
",
- "vaccinated.reason": "Your status changed to Green because your vaccination is already effective.",
+ "vaccinated.notice": "Your COVID-19 vaccination has been verified",
+ "vaccinated.reason": "We have a verified record of your completed COVID-19 vaccination on file; your status has changed to Green.",
+ "vaccinated.suspended.notice": "Your vaccination status is replaced by testing for compliance and building access.",
+ "vaccinated.suspended.reason": "Your status change to Orange because you have been identified as being in an area with a significant increase in positive COVID-19 cases over a short period of time.",
"quarantine-on.step": "Stay at home and avoid contacts",
"quarantine-on.reason": "Your status changed to Orange because the Public Health department placed you in Quarantine.",
"exempt-on.step": "You are exempted from testing",
@@ -892,8 +1042,10 @@
"positive-nip.step.html": "No es necesario que se aísle a sí mismo a menos que tenga síntomas similares a los de COVID y su profesional de la salud autorizado le indique que lo haga.
",
"positive-nip.explanation": "Su prueba de PCR de saliva muestra que el VIRUS ESTÁ DETECTADO en su REPETICIÓN POSITIVA en un NIVEL NO INFECCIOSO.",
"test.monitor.step.html": "Controle los resultados de su prueba
La universidad le anima a vacunarse si puede hacerlo. Visite vacunafinder.org para encontrar citas cercanas.
",
+ "test.monitor.vaccinated.step.html": "Controle los resultados de su prueba
Tenemos un registro verificado de su vacunación COVID-19 completa en el archivo.
Se ha identificado que se encuentra en un área con un aumento significativo de casos positivos de COVID-19 durante un corto período de tiempo. Debe recibir una prueba COVID-19 en el campus para mantener el cumplimiento y tener el estado de Acceso al edificio “Concedido” hasta que los casos mejoren y se le notifique lo contrario.
",
"test.now.step": "Haz una prueba ahora",
"test.now.reason": "Su estado cambió a Naranja porque está atrasado en un examen.",
+ "test.now.vaccinated.step.html": "Hágase una prueba ahora.
Se ha identificado que se encuentra en un área con un aumento significativo de casos positivos de COVID-19 durante un corto período de tiempo. A partir de ahora, debe recibir una prueba de COVID-19 en el campus cada dos días (incluso si está completamente vacunado y su registro de vacunación ha sido verificado) para mantener el cumplimiento y tener el estado de Acceso al edificio “Otorgado” hasta que los casos mejoren y usted se notifica de otra manera.
",
"test.another.asap.step": "Obtenga otra prueba lo antes posible",
"test.another.now.step.html": "Obtenga su segunda prueba ahora. Debes de tomar dos pruebas en el campus antes del 25 de enero.
- Separe las pruebas con tres días de cuarentena: si la primera prueba es el día uno, la segunda prueba será el día cinco.
- Limítese a las actividades esenciales hasta que obtenga el segundo resultado negativo.
- El acceso al edificio cambiará a Concedido (amarillo) con el segundo resultado negativo de la prueba.
Ver el calendario y las reglas de las pruebas.
",
"test.after.step.html": "Obtenga su segunda prueba después del {next_step_date}. Debes de tomar dos pruebas en el campus antes del 25 de enero.
- Separe las pruebas con tres días de cuarentena: si la primera prueba es el día uno, la segunda prueba será el día cinco.
- Limítese a las actividades esenciales hasta que obtenga el segundo resultado negativo.
- El acceso al edificio cambiará a Concedido (amarillo) con el segundo resultado negativo de la prueba.
Ver el calendario y las reglas de las pruebas.
",
@@ -908,7 +1060,10 @@
"exposure.step.html": "Es probable que haya estado expuesto a una persona infectada con COVID-19.
- Debe ponerse en cuarentena inmediatamente.
- Quedarse en casa. No vaya al trabajo, la escuela o áreas públicas.
- Sepárate de las demás en tu casa.
- Póngase en contacto con covidwellness@illinois.edu para obtener orientación.
- Hágase la prueba después del {next_step_date} para ver si ha desarrollado la enfermedad.
- Más información: Cuarentena y aislamiento
",
"exposure.reason": "Su estado cambió a Naranja porque recibió una notificación de exposición.",
"vaccinated.step.html": "Tenemos un registro verificado de su vacunación COVID-19 completa en el archivo.
Su estado de vacunación reemplaza las pruebas de cumplimiento y acceso al edificio hasta nuevo aviso.
Hágase una prueba de COVID-19 en el campus si experimenta síntomas.
Continúe monitoreando las comunicaciones de la universidad para detectar cualquier cambio en su política de exámenes.
",
- "vaccinated.reason": "Su estado cambió a Verde porque su vacunación ya es efectiva.",
+ "vaccinated.notice": "Se ha verificado su vacuna COVID-19.",
+ "vaccinated.reason": "Tenemos un registro verificado de su vacunación COVID-19 completa en el archivo; su estado ha cambiado a verde.",
+ "vaccinated.suspended.notice": "Su estado de vacunación se reemplaza por pruebas de cumplimiento y acceso al edificio.",
+ "vaccinated.suspended.reason": "Su estado cambia a Naranja porque se ha identificado que se encuentra en un área con un aumento significativo de casos positivos de COVID-19 durante un corto período de tiempo.",
"quarantine-on.step": "Quédese en casa y evite los contactos",
"quarantine-on.reason": "Su estado cambió a Orange porque el departamento de Salud Pública lo puso en cuarentena.",
"exempt-on.step": "Estas exenta de pruebas",
@@ -963,8 +1118,10 @@
"positive-nip.step.html": "除非您有類似COVID的症狀並且由您的有執照的衛生專業人員指示這樣做,否則您無需自我隔離。
",
"positive-nip.explanation": "您的唾液PCR測試顯示病毒在非陽性水平的重複陽性中被檢測到。",
"test.monitor.step.html": "監控您的測試結果
如果您有能力,大學鼓勵您接種疫苗。 訪問 vaccinefinder.org 查找附近的約會。
",
+ "test.monitor.vaccinated.step.html": "監控您的測試結果
我們記錄了您完成的COVID-19疫苗接種的經過驗證的記錄。
您已被確定為在短時間內 COVID-19 陽性病例顯著增加的地區。 您需要接受校內 COVID-19 測試以保持合規性並具有“授予”建築物訪問權限狀態,直到情況有所改善並且您收到其他通知。
",
"test.now.step": "立即獲得測試",
"test.now.reason": "您的狀態更改為“橙色”,因為您已逾期進行測試。",
+ "test.now.vaccinated.step.html": "立即進行測試。
您已被確定為在短時間內 COVID-19 陽性病例顯著增加的地區。 從現在開始,您必須每隔一天接受一次校內 COVID-19 測試(即使您已完全接種疫苗並且您的疫苗接種記錄已得到驗證)以保持合規性並具有“授予”建築物訪問權限狀態,直到病例有所改善並且您 另行通知。
",
"test.another.asap.step": "盡快獲得另一個測試",
"test.another.now.step.html": "现在进行第二次测试. 你必须在1月25日之前检测两次.
- 两次测试之间需隔离三天:如果第一次测试在第一天,第二次测试将在第五天
- 在得到第二个阴性测试结果之前,尽量只做必要的出行
- 在第二个阴性结果出来后,您的建筑访问权限将更改为“已授予”(黄色)
见测试计划和规则.
",
"test.after.step.html": "在{next_step_date}之后进行第二次测试. 你必须在1月25日之前检测两次.
- 两次测试之间需隔离三天:如果第一次测试在第一天,第二次测试将在第五天
- 在得到第二个阴性测试结果之前,尽量只做必要的出行
- 在第二个阴性结果出来后,您的建筑访问权限将更改为“已授予”(黄色)
见测试计划和规则.
",
@@ -979,7 +1136,10 @@
"exposure.step.html": "您可能已經接觸了感染了COVID-19的人。
",
"exposure.reason": "您的狀態更改為橙色,因為您收到了曝光通知。",
"vaccinated.step.html": "我們記錄了您完成的COVID-19疫苗接種的經過驗證的記錄。
您的疫苗接種狀態將取代合規性測試和建立訪問權限,直至另行通知。
如果您出現症狀,請進行校園 COVID-19 測試。
繼續監控大學通訊,了解您的考試政策是否有任何變化。
",
- "vaccinated.reason": "您的狀態已更改為“綠色”,因為您的疫苗接種已經有效。",
+ "vaccinated.notice": "COVID-19ワクチン接種が確認されました。",
+ "vaccinated.reason": "我們有您完成的 COVID-19 疫苗接種的經過驗證的記錄存檔; 您的狀態已更改為綠色。",
+ "vaccinated.suspended.notice": "您的疫苗接種狀態將被合規性測試和建築物訪問所取代。",
+ "vaccinated.suspended.reason": "您的狀態更改為橙色,因為您已被確定為在短時間內 COVID-19 陽性病例顯著增加的地區。",
"quarantine-on.step": "呆在家裡,避免接觸",
"quarantine-on.reason": "您的狀態更改為“橙色”,因為公共衛生部門已將您隔離。",
"exempt-on.step": "您免於測試",
@@ -1034,8 +1194,10 @@
"positive-nip.step.html": "認可された医療従事者から指示された場合以外は自己隔離する必要やキャンパスで行う検査を60日間受ける必要はありません。Safer Illinoisアプリは、この期間にアクセスを許可するように設定されています。質問や困ったことがある場合は、covidwellness@illinois.edu まで連絡してください。
",
"positive-nip.explanation": "唾液PCR検査は、不感染性レベルにある場合、2回目の検査結果が陽性検査を出す可能性があります。",
"test.monitor.step.html": "検査結果をモニタリングする
大学は、可能であれば予防接種を受けることを推奨しています。 vaccinefinder.org にアクセスして、近くの予定を見つけてください。
",
+ "test.monitor.vaccinated.step.html": "検査結果をモニタリングする
完了したCOVID-19ワクチン接種の確認済みの記録がファイルにあります。
あなたは、短期間に陽性のCOVID-19症例が大幅に増加している地域にいると特定されました。 コンプライアンスを維持するためにキャンパス内のCOVID-19テストを受け、ケースが改善されて別の方法で通知されるまで「許可された」建物アクセスステータスを取得する必要があります。
",
"test.now.step": "今検査を受ける",
"test.now.reason": "検査期日が過ぎたため、ステータスがオレンジに変更されました。",
+ "test.now.vaccinated.step.html": "今すぐテストを受けてください。
あなたは、短期間に陽性のCOVID-19症例が大幅に増加している地域にいると特定されました。 今から、コンプライアンスを維持し、ケースが改善してあなたが それ以外の場合は通知されます。
",
"test.another.asap.step": "今すぐ別の検査を受ける",
"test.another.now.step.html": "2回目の検査をいま受ける。
- 2回目の陰性結果を受け取るまでは必要な行動だけに制限してください。
- 2回目の陰性結果とともに建物に入る許可が「GRANTED」(黄)に変更されます。
で検査予定とルールについての情報を確認してください。.
",
"test.after.step.html": "2回目の検査を{next_step_date}の後で受ける。 1月25日までにキャンプの検査を2回受ける必要があります。
- 2つの検査の間に、3日間の隔離期間を設けて下さい。: 1回目の検査を初日に受けた場合、2回目の検査は5日目になります。
- 2回目の陰性結果を受けるまでには必要な行動だけに制限してください。
- 2回目の陰性結果とともに建物に入る許可が「GRANTED」(黄)に変更されます。
で検査予定とルールについての情報を確認してください。.
",
@@ -1050,7 +1212,10 @@
"exposure.step.html": "COVID-19に感染している人と接触した可能性があります。
- 直ちに隔離してください。
- 家にいてください。仕事や学校や公共の場へ行かないでください。
- 家にいる人から離れてください。
- ガイダンスを受けるにはcovidwellness@illinois.eduに連絡してください。
- {next_step_date}の後で検査を受けて、自分が発症しているかどうかを確認してください。
- より詳しい情報: 隔離と孤立
",
"exposure.reason": "接触通知を受信したため、ステータスがオレンジに変更されました。",
"vaccinated.step.html": "完了したCOVID-19ワクチン接種の確認済みの記録がファイルにあります。
あなたのワクチン接種状況は、追って通知があるまで、コンプライアンスと建物へのアクセスのテストに取って代わります。
症状が出た場合は、キャンパス内のCOVID-19検査を受けてください。
テストポリシーに変更がないか、大学のコミュニケーションを引き続き監視します。
",
- "vaccinated.reason": "予防接種がすでに有効になっているため、ステータスが緑に変わりました。",
+ "vaccinated.notice": "您的 COVID-19 疫苗接種已通過驗證。",
+ "vaccinated.reason": "完了したCOVID-19ワクチン接種の確認済みの記録がファイルにあります。 ステータスが緑に変わりました。",
+ "vaccinated.suspended.notice": "予防接種のステータスは、コンプライアンスと建物へのアクセスのテストに置き換えられます。",
+ "vaccinated.suspended.reason": "短期間に陽性のCOVID-19症例が大幅に増加している地域にいることが確認されたため、ステータスがオレンジに変わります。",
"quarantine-on.step": "家にいて、他の人との接触を避けてください。",
"quarantine-on.reason": "Public Health departmentにより隔離状態に置かれているため、ステータスがオレンジに変更されました。",
"exempt-on.step": "あなたはテストから免除されています",
diff --git a/assets/strings.en.json b/assets/strings.en.json
index 3dba4dc8..4ffe7914 100644
--- a/assets/strings.en.json
+++ b/assets/strings.en.json
@@ -164,11 +164,11 @@
"panel.onboarding.base.not_now.hint": "",
"panel.onboarding.base.not_now.title": "Not right now",
- "panel.settings.feedback.label.title": "Provide Feedback",
+ "panel.settings.get_help.label.title": "Get Help",
"panel.settings.privacy_statement.label.title": "Privacy Statement",
- "panel.settings.label.offline.feedback": "Providing a feedback is not available while offline.",
+ "panel.settings.label.offline.get_help": "Getting a help is not available while offline.",
"panel.settings.home.settings.header": "Settings",
"panel.settings.home.connect.not_logged_in.title": "Connect to Illinois",
@@ -213,10 +213,8 @@
"panel.settings.home.button.debug.hint": "",
"panel.settings.home.button.test.title": "Test",
"panel.settings.home.button.test.hint": "",
- "panel.settings.home.feedback.title": "We need your ideas!",
- "panel.settings.home.feedback.description": "Enjoying the app? Missing something? Tap on the bottom to submit your idea.",
- "panel.settings.home.button.feedback.title": "Submit Feedback",
- "panel.settings.home.button.feedback.hint": "",
+ "panel.settings.home.button.get_help.title": "Get Help",
+ "panel.settings.home.button.get_help.hint": "",
"panel.settings.home.covid19.exposure_notifications": "Exposure Notifications",
"panel.settings.home.covid19.provider_test_result": "Health Provider Test Results",
"panel.settings.home.covid19.provider_vaccine_info": "Health Provider Vaccine Information",
@@ -327,7 +325,6 @@
"panel.covid19home.label.contact_trace.title": "Contact Trace",
"panel.covid19home.label.reported_symptoms.title": "Self Reported Symptoms",
"panel.covid19home.label.vaccine.effective.title": "Vaccine Effective",
- "panel.covid19home.label.vaccine.taken.title": "Vaccine Taken",
"panel.covid19home.label.vaccine.title": "Vaccine",
"panel.covid19home.button.info.title": "Info ",
"panel.covid19home.label.access.granted": "Building access granted",
@@ -338,6 +335,14 @@
"panel.covid19home.button.groups.title": "Groups",
"panel.covid19home.button.groups.hint": "",
+ "panel.covid19home.vaccination.heading.title": "VACCINATION",
+ "panel.covid19home.vaccination.none.title": "Get a vaccine now",
+ "panel.covid19home.vaccination.none.description": "• COVID-19 vaccines are safe\n• COVID-19 vaccines are effective\n• COVID-19 vaccines allow you to safely do more\n• COVID-19 vaccines build safer protection",
+ "panel.covid19home.vaccination.vaccinated.title": "Vaccinated",
+ "panel.covid19home.vaccination.vaccinated.description": "Your vaccination is not effective yet.",
+ "panel.covid19home.vaccination.button.appointment.title": "Make an appointment",
+ "panel.covid19home.vaccination.button.appointment.hint": "",
+
"panel.covid19_test_locations.header.title": "Test Locations",
"panel.covid19_test_locations.label.contact.title": "Contact",
"panel.covid19_test_locations.distance.text": "mi away",
@@ -611,7 +616,6 @@
"panel.health.covid19.history.label.contact_trace.details": "contact trace: ",
"panel.health.covid19.history.label.vaccine.effective.title": "Vaccine Effective",
"panel.health.covid19.history.label.vaccine.effective.details": "vaccine effective: ",
- "panel.health.covid19.history.label.vaccine.taken.title": "Vaccine Taken",
"panel.health.covid19.history.label.vaccine.taken.details": "vaccine taken: ",
"panel.health.covid19.history.label.vaccine.title": "Vaccine",
"panel.health.covid19.history.label.vaccine.details": "vaccine: ",
@@ -626,7 +630,7 @@
"panel.health.covid19.history.label.verified": "Verified",
"panel.health.covid19.history.label.verification_pending": "Verification Pending",
"panel.health.covid19.history.message.clear_failed": "Failed to clear COVID-19 event history",
- "panel.health.covid19.history.button.repost_history.title": "Request my latest test again",
+ "panel.health.covid19.history.button.repost_history.title": "Request my vaccine and latest test again",
"panel.health.covid19.history.button.repost_history.hint": "",
"panel.health.covid19.history.message.request_tests": "Your request has been submitted. You should receive your latest test within an hour",
@@ -708,8 +712,7 @@
"panel.health.status_update.label.reason.symptoms.title": "You reported new symptoms",
"panel.health.status_update.label.reason.exposed.title": "You were exposed to someone who was likely infected",
"panel.health.status_update.label.reason.exposure.detail": "Duration of exposure: ",
- "panel.health.status_update.label.reason.vaccine.effective.title": "Your vaccine is already effective.",
- "panel.health.status_update.label.reason.vaccine.taken.title": "Your vaccine is taken.",
+ "panel.health.status_update.label.reason.vaccine.effective.title": "Your COVID-19 vaccination has been verified.",
"panel.health.status_update.label.reason.vaccine.title": "Your vaccine is applied.",
"panel.health.status_update.label.reason.action.title": "Health authorities require you to take an action.",
"panel.health.status_update.label.reason.action.detail": "Action Required: ",
diff --git a/assets/strings.es.json b/assets/strings.es.json
index de38287e..84578310 100644
--- a/assets/strings.es.json
+++ b/assets/strings.es.json
@@ -164,11 +164,11 @@
"panel.onboarding.base.not_now.hint": "",
"panel.onboarding.base.not_now.title": "No en este momento",
- "panel.settings.feedback.label.title": "Proporcionar comentarios",
+ "panel.settings.get_help.label.title": "Consigue Ayuda",
"panel.settings.privacy_statement.label.title": "Declaración de privacidad",
- "panel.settings.label.offline.feedback": "Proporcionar un comentario no está disponible sin conexión.",
+ "panel.settings.label.offline.get_help": "No es posible obtener ayuda sin conexión.",
"panel.settings.home.settings.header": "Configuración",
"panel.settings.home.connect.not_logged_in.title": "Conéctate a Illinois",
@@ -213,10 +213,8 @@
"panel.settings.home.button.debug.hint": "",
"panel.settings.home.button.test.title": "Prueba",
"panel.settings.home.button.test.hint": "",
- "panel.settings.home.feedback.title": "¡Necesitamos tus ideas!",
- "panel.settings.home.feedback.description": "¿Estás disfrutando la aplicación? ¿Echando de menos algo? Toque en la parte inferior para enviar su idea",
- "panel.settings.home.button.feedback.title": "Enviar comentarios",
- "panel.settings.home.button.feedback.hint": "",
+ "panel.settings.home.button.get_help.title": "Consigue Ayuda",
+ "panel.settings.home.button.get_help.hint": "",
"panel.settings.home.covid19.exposure_notifications": "Notificaciones de exposición",
"panel.settings.home.covid19.provider_test_result": "Resultados de la prueba del proveedor de salud",
"panel.settings.home.covid19.provider_vaccine_info": "Información sobre vacunas del proveedor de salud",
@@ -327,7 +325,6 @@
"panel.covid19home.label.contact_trace.title": "Seguimiento de contacto",
"panel.covid19home.label.reported_symptoms.title": "Síntomas autoinformados",
"panel.covid19home.label.vaccine.effective.title": "Vacuna Eficaz",
- "panel.covid19home.label.vaccine.taken.title": "Vacuna Tomada",
"panel.covid19home.label.vaccine.title": "Vacuna",
"panel.covid19home.button.info.title": "Información ",
"panel.covid19home.label.access.granted": "Acceso al edificio concedido",
@@ -338,6 +335,14 @@
"panel.covid19home.button.groups.title": "Groups",
"panel.covid19home.button.groups.hint": "",
+ "panel.covid19home.vaccination.heading.title": "VACUNACIÓN",
+ "panel.covid19home.vaccination.none.title": "Obtenga una vacuna ahora",
+ "panel.covid19home.vaccination.none.description": "• Las vacunas COVID-19 son seguras\n• Las vacunas COVID-19 son efectivas\n• Las vacunas COVID-19 le permiten hacer más de manera segura\n• Las vacunas COVID-19 crean una protección más segura",
+ "panel.covid19home.vaccination.vaccinated.title": "Vacunado",
+ "panel.covid19home.vaccination.vaccinated.description": "Su vacunación aún no es efectiva.",
+ "panel.covid19home.vaccination.button.appointment.title": "Haga una cita",
+ "panel.covid19home.vaccination.button.appointment.hint": "",
+
"panel.covid19_test_locations.header.title": "Lugares de prueba",
"panel.covid19_test_locations.label.contact.title": "Contacto",
"panel.covid19_test_locations.distance.text": "mi distancia",
@@ -611,7 +616,6 @@
"panel.health.covid19.history.label.contact_trace.details": "seguimiento de contacto: ",
"panel.health.covid19.history.label.vaccine.effective.title": "Vacuna Eficaz",
"panel.health.covid19.history.label.vaccine.effective.details": "vacuna eficaz: ",
- "panel.health.covid19.history.label.vaccine.taken.title": "Vacuna Tomada",
"panel.health.covid19.history.label.vaccine.taken.details": "vacuna tomada: ",
"panel.health.covid19.history.label.vaccine.title": "Vacuna",
"panel.health.covid19.history.label.vaccine.details": "vacuna: ",
@@ -626,7 +630,7 @@
"panel.health.covid19.history.label.verified": "Verificado",
"panel.health.covid19.history.label.verification_pending": "Verificación pendiente",
"panel.health.covid19.history.message.clear_failed": "No se pudo borrar el historial de eventos de COVID-19",
- "panel.health.covid19.history.button.repost_history.title": "Solicitar mi última prueba nuevamente",
+ "panel.health.covid19.history.button.repost_history.title": "Solicitar mi vacuna y la última prueba nuevamente",
"panel.health.covid19.history.button.repost_history.hint": "",
"panel.health.covid19.history.message.request_tests": "Su solicitud ha sido enviada. Debería recibir su última prueba en una hora",
@@ -708,8 +712,7 @@
"panel.health.status_update.label.reason.symptoms.title": "Reportaste nuevos síntomas",
"panel.health.status_update.label.reason.exposed.title": "Estuvo expuesto a alguien que probablemente estaba infectado",
"panel.health.status_update.label.reason.exposure.detail": "Duración de exposición:",
- "panel.health.status_update.label.reason.vaccine.effective.title": "Tu vacuna ya es eficaz.",
- "panel.health.status_update.label.reason.vaccine.taken.title": "Se toma su vacuna.",
+ "panel.health.status_update.label.reason.vaccine.effective.title": "Se ha verificado su vacuna COVID-19.",
"panel.health.status_update.label.reason.vaccine.title": "Se aplica su vacuna.",
"panel.health.status_update.label.reason.action.title": "Las autoridades sanitarias le exigen que actúe.",
"panel.health.status_update.label.reason.action.detail": "Acción requerida:",
diff --git a/assets/strings.ja.json b/assets/strings.ja.json
index f685c753..2b28685b 100644
--- a/assets/strings.ja.json
+++ b/assets/strings.ja.json
@@ -164,11 +164,11 @@
"panel.onboarding.base.not_now.hint": "",
"panel.onboarding.base.not_now.title": "今はしない",
- "panel.settings.feedback.label.title": "Provide Feedback",
+ "panel.settings.get_help.label.title": "得到幫助",
"panel.settings.privacy_statement.label.title": "Privacy Statement",
- "panel.settings.label.offline.feedback": "Providing a feedback is not available while offline.",
+ "panel.settings.label.offline.get_help": "オフライン中はヘルプを利用できません。",
"panel.settings.home.settings.header": "設定",
"panel.settings.home.connect.not_logged_in.title": "Connect to Illinois",
@@ -213,10 +213,8 @@
"panel.settings.home.button.debug.hint": "",
"panel.settings.home.button.test.title": "Test",
"panel.settings.home.button.test.hint": "",
- "panel.settings.home.feedback.title": "We need your ideas!",
- "panel.settings.home.feedback.description": "Enjoying the app? Missing something? Tap on the bottom to submit your idea.",
- "panel.settings.home.button.feedback.title": "Submit Feedback",
- "panel.settings.home.button.feedback.hint": "",
+ "panel.settings.home.button.get_help.title": "助けを得ます",
+ "panel.settings.home.button.get_help.hint": "",
"panel.settings.home.covid19.exposure_notifications": "接続通知",
"panel.settings.home.covid19.provider_test_result": "医療提供者のテスト結果",
"panel.settings.home.covid19.provider_vaccine_info": "医療提供者のワクチン情報",
@@ -327,7 +325,6 @@
"panel.covid19home.label.contact_trace.title": "Contact Trace",
"panel.covid19home.label.reported_symptoms.title": "Self Reported Symptoms",
"panel.covid19home.label.vaccine.effective.title": "Vaccine Effective",
- "panel.covid19home.label.vaccine.taken.title": "Vaccine Taken",
"panel.covid19home.label.vaccine.title": "Vaccine",
"panel.covid19home.button.info.title": "情報 ",
"panel.covid19home.label.access.granted": "建物への出入りが承認されました",
@@ -338,6 +335,14 @@
"panel.covid19home.button.groups.title": "グループ",
"panel.covid19home.button.groups.hint": "",
+ "panel.covid19home.vaccination.heading.title": "ワクチン",
+ "panel.covid19home.vaccination.none.title": "今すぐワクチンを入手",
+ "panel.covid19home.vaccination.none.description": "• COVID-19 疫苗是安全的\n• COVID-19ワクチンは効果的です\n• COVID-19ワクチンはあなたが安全にもっと多くのことをすることを可能にします\n• COVID-19ワクチンはより安全な保護を構築します",
+ "panel.covid19home.vaccination.vaccinated.title": "ワクチン接種",
+ "panel.covid19home.vaccination.vaccinated.description": "あなたの予防接種はまだ効果的ではありません。",
+ "panel.covid19home.vaccination.button.appointment.title": "予約する",
+ "panel.covid19home.vaccination.button.appointment.hint": "",
+
"panel.covid19_test_locations.header.title": "検査現場",
"panel.covid19_test_locations.label.contact.title": "Contact",
"panel.covid19_test_locations.distance.text": "mi away",
@@ -611,7 +616,6 @@
"panel.health.covid19.history.label.contact_trace.details": "contact trace: ",
"panel.health.covid19.history.label.vaccine.effective.title": "Vaccine Effective",
"panel.health.covid19.history.label.vaccine.effective.details": "vaccine effective: ",
- "panel.health.covid19.history.label.vaccine.taken.title": "Vaccine Taken",
"panel.health.covid19.history.label.vaccine.taken.details": "vaccine taken: ",
"panel.health.covid19.history.label.vaccine.title": "Vaccine",
"panel.health.covid19.history.label.vaccine.details": "vaccine: ",
@@ -626,7 +630,7 @@
"panel.health.covid19.history.label.verified": "確認しました",
"panel.health.covid19.history.label.verification_pending": "Verification Pending",
"panel.health.covid19.history.message.clear_failed": "Failed to clear COVID-19 event history",
- "panel.health.covid19.history.button.repost_history.title": "最新の検査結果を再度要求する",
+ "panel.health.covid19.history.button.repost_history.title": "ワクチンと最新の検査をもう一度リクエストしてください",
"panel.health.covid19.history.button.repost_history.hint": "",
"panel.health.covid19.history.message.request_tests": "要求は提出されました。1時間以内に最新の検査結果を受け取るはずです",
@@ -708,8 +712,7 @@
"panel.health.status_update.label.reason.symptoms.title": "You reported new symptoms",
"panel.health.status_update.label.reason.exposed.title": "You were exposed to someone who was likely infected",
"panel.health.status_update.label.reason.exposure.detail": "Duration of exposure: ",
- "panel.health.status_update.label.reason.vaccine.effective.title": "Your vaccine is already effective.",
- "panel.health.status_update.label.reason.vaccine.taken.title": "Your vaccine is taken.",
+ "panel.health.status_update.label.reason.vaccine.effective.title": "Your COVID-19 vaccination has been verified.",
"panel.health.status_update.label.reason.vaccine.title": "Your vaccine is applied.",
"panel.health.status_update.label.reason.action.title": "Health authorities require you to take an action.",
"panel.health.status_update.label.reason.action.detail": "Action Required: ",
diff --git a/assets/strings.zh.json b/assets/strings.zh.json
index 251b5122..2c74cf7e 100644
--- a/assets/strings.zh.json
+++ b/assets/strings.zh.json
@@ -164,11 +164,11 @@
"panel.onboarding.base.not_now.hint": "",
"panel.onboarding.base.not_now.title": "暂时不",
- "panel.settings.feedback.label.title": "提供反馈",
+ "panel.settings.get_help.label.title": "得到幫助",
"panel.settings.privacy_statement.label.title": "隐私声明",
- "panel.settings.label.offline.feedback": "离线时无法提供反馈。",
+ "panel.settings.label.offline.get_help": "離線時無法獲得幫助。",
"panel.settings.home.settings.header": "设置",
"panel.settings.home.connect.not_logged_in.title": "連接到伊利諾伊州",
@@ -213,10 +213,8 @@
"panel.settings.home.button.debug.hint": "",
"panel.settings.home.button.test.title": "测试",
"panel.settings.home.button.test.hint": "",
- "panel.settings.home.feedback.title": " 我们需要你的主意!",
- "panel.settings.home.feedback.description": " 喜欢这个应用程序吗?我们遗漏了什么?点击底部提交您的想法。",
- "panel.settings.home.button.feedback.title": "提交反馈",
- "panel.settings.home.button.feedback.hint": "",
+ "panel.settings.home.button.get_help.title": "得到幫助",
+ "panel.settings.home.button.get_help.hint": "",
"panel.settings.home.covid19.exposure_notifications": "接触通知",
"panel.settings.home.covid19.provider_test_result": "健康提供者測試結果",
"panel.settings.home.covid19.provider_vaccine_info": "衛生提供者疫苗信息",
@@ -327,7 +325,6 @@
"panel.covid19home.label.contact_trace.title": "接触痕迹",
"panel.covid19home.label.reported_symptoms.title": "自述症状",
"panel.covid19home.label.vaccine.effective.title": "疫苗有效",
- "panel.covid19home.label.vaccine.taken.title": "接種疫苗",
"panel.covid19home.label.vaccine.title": "疫苗",
"panel.covid19home.button.info.title": "信息 ",
"panel.covid19home.label.access.granted": "授予建築物訪問權限",
@@ -338,6 +335,14 @@
"panel.covid19home.button.groups.title": "Groups",
"panel.covid19home.button.groups.hint": "",
+ "panel.covid19home.vaccination.heading.title": "疫苗接種",
+ "panel.covid19home.vaccination.none.title": "立即接種疫苗",
+ "panel.covid19home.vaccination.none.description": "• COVID-19 疫苗是安全的\n• COVID-19 疫苗是有效的\n• COVID-19 疫苗可讓您安全地做更多事情\n• COVID-19 疫苗可建立更安全的保護",
+ "panel.covid19home.vaccination.vaccinated.title": "已接種",
+ "panel.covid19home.vaccination.vaccinated.description": "您的疫苗接種尚未生效。",
+ "panel.covid19home.vaccination.button.appointment.title": "預約",
+ "panel.covid19home.vaccination.button.appointment.hint": "",
+
"panel.covid19_test_locations.header.title": "测试位置",
"panel.covid19_test_locations.label.contact.title": "联系人",
"panel.covid19_test_locations.distance.text": "英里遠",
@@ -611,7 +616,6 @@
"panel.health.covid19.history.label.contact_trace.details": "接触者追踪: ",
"panel.health.covid19.history.label.vaccine.effective.title": "疫苗有效",
"panel.health.covid19.history.label.vaccine.effective.details": "疫苗有效: ",
- "panel.health.covid19.history.label.vaccine.taken.title": "接種疫苗",
"panel.health.covid19.history.label.vaccine.taken.details": "接種疫苗: ",
"panel.health.covid19.history.label.vaccine.title": "疫苗",
"panel.health.covid19.history.label.vaccine.details": "疫苗: ",
@@ -626,7 +630,7 @@
"panel.health.covid19.history.label.verified": "已验证",
"panel.health.covid19.history.label.verification_pending": "等待验证",
"panel.health.covid19.history.message.clear_failed": "无法清除COVID-19事件历史记录",
- "panel.health.covid19.history.button.repost_history.title": "再次请求我的最新测试",
+ "panel.health.covid19.history.button.repost_history.title": "再次請求我的疫苗和最新測試",
"panel.health.covid19.history.button.repost_history.hint": "",
"panel.health.covid19.history.message.request_tests": "您的请求已提.你应该在一小时内收到你的最新测验",
@@ -708,8 +712,7 @@
"panel.health.status_update.label.reason.symptoms.title": "你报告了新的症状",
"panel.health.status_update.label.reason.exposed.title": "你接触过可能被感染的人",
"panel.health.status_update.label.reason.exposure.detail": "暴露时间: ",
- "panel.health.status_update.label.reason.vaccine.effective.title": "您的疫苗已經有效。",
- "panel.health.status_update.label.reason.vaccine.taken.title": "您的疫苗已經服用。",
+ "panel.health.status_update.label.reason.vaccine.effective.title": "您的 COVID-19 疫苗接種已通過驗證。",
"panel.health.status_update.label.reason.vaccine.title": "您的疫苗已接種。",
"panel.health.status_update.label.reason.action.title": "衛生當局要求您採取行動。",
"panel.health.status_update.label.reason.action.detail": "待办行动: ",
diff --git a/ios/Podfile b/ios/Podfile
index 6fa5a192..07dff8c1 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -34,7 +34,6 @@ target 'Runner' do
pod 'GoogleMaps', '3.3.0'
pod 'ZXingObjC', '3.6.4'
- pod 'PPBlinkID', '~> 5.3.0'
pod 'HKDFKit', '0.0.3'
# 'Firebase/MLVisionBarcodeModel' is required by 'firebase_ml_vision' plugin from pubspec.yaml.
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 48f3625a..fc4f09cf 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -621,7 +621,6 @@
"${BUILT_PRODUCTS_DIR}/HKDFKit/HKDFKit.framework",
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework",
- "${PODS_ROOT}/PPBlinkID/Microblink.framework",
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
"${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
"${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework",
@@ -667,7 +666,6 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Microblink.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework",
@@ -709,12 +707,10 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
- "${PODS_ROOT}/PPBlinkID/Microblink.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Microblink.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m
index 3e24864b..5022f994 100644
--- a/ios/Runner/AppDelegate.m
+++ b/ios/Runner/AppDelegate.m
@@ -38,7 +38,6 @@
#import
#import
#import
-#import
#import
#import
@@ -59,7 +58,7 @@ @interface LaunchScreenView : UIView
UIInterfaceOrientation _interfaceOrientationFromMask(UIInterfaceOrientationMask value);
UIInterfaceOrientationMask _interfaceOrientationToMask(UIInterfaceOrientation value);
-@interface AppDelegate() {
+@interface AppDelegate() {
}
// Flutter
@@ -74,13 +73,6 @@ @interface AppDelegate() implements NotificationsListener {
+ String _lastRunVersion;
String _upgradeRequiredVersion;
String _upgradeAvailableVersion;
Key key = UniqueKey();
@@ -128,6 +129,7 @@ class _AppState extends State implements NotificationsListener {
Onboarding.notifyFinished,
Config.notifyUpgradeAvailable,
Config.notifyUpgradeRequired,
+ Config.notifyOnboardingRequired,
Organizations.notifyOrganizationChanged,
Organizations.notifyEnvironmentChanged,
UserProfile.notifyProfileDeleted,
@@ -135,18 +137,13 @@ class _AppState extends State implements NotificationsListener {
AppLivecycle.instance.ensureBinding();
+ _lastRunVersion = Storage().lastRunVersion;
_upgradeRequiredVersion = Config().upgradeRequiredVersion;
_upgradeAvailableVersion = Config().upgradeAvailableVersion;
- // This is just a placeholder to take some action on app upgrade.
- String lastRunVersion = Storage().lastRunVersion;
- if ((lastRunVersion == null) || (lastRunVersion != Config().appVersion)) {
-
- // Force unboarding to concent vaccination (#651)
- if (AppVersion.compareVersions(lastRunVersion, '2.10.28') < 0) {
- Storage().onBoardingPassed = false;
- }
-
+ _checkForceOnboarding();
+
+ if ((_lastRunVersion == null) || (_lastRunVersion != Config().appVersion)) {
Storage().lastRunVersion = Config().appVersion;
}
@@ -211,6 +208,20 @@ class _AppState extends State implements NotificationsListener {
Navigator.pushAndRemoveUntil(context, routeToHome, (_) => false);
}
+ bool _checkForceOnboarding() {
+ // Action: Force unboarding to concent vaccination (#651, #681)
+ String onboardingRequiredVersion = Config().onboardingRequiredVersion;
+ if ((Storage().onBoardingPassed == true) &&
+ (_lastRunVersion != null) &&
+ (onboardingRequiredVersion != null) &&
+ (AppVersion.compareVersions(_lastRunVersion, onboardingRequiredVersion) < 0) &&
+ (AppVersion.compareVersions(onboardingRequiredVersion, Config().appVersion) <= 0)) {
+ Storage().onBoardingPassed = false;
+ return true;
+ }
+ return false;
+ }
+
// NotificationsListener
@override
@@ -228,6 +239,11 @@ class _AppState extends State implements NotificationsListener {
_upgradeAvailableVersion = param;
});
}
+ else if (name == Config.notifyOnboardingRequired) {
+ if (_checkForceOnboarding()) {
+ _resetUI();
+ }
+ }
else if (name == Organizations.notifyOrganizationChanged) {
_resetUI();
}
diff --git a/lib/model/Health.dart b/lib/model/Health.dart
index 4afb08cd..16349a49 100644
--- a/lib/model/Health.dart
+++ b/lib/model/Health.dart
@@ -126,13 +126,17 @@ class HealthStatusBlob {
final String nextStepHtml;
final DateTime nextStepDateUtc;
+ final String warning;
+ final String warningHtml;
+
final String eventExplanation;
final String eventExplanationHtml;
- final String warning;
- final String warningHtml;
+ final String statusUpdateNotice;
+ final String statusUpdateNoticeHtml;
- final String reason;
+ final String statusUpdateReason;
+ final String statusUpdateReasonHtml;
final dynamic fcmTopic;
@@ -141,7 +145,13 @@ class HealthStatusBlob {
static const String _nextStepDateMacro = '{next_step_date}';
static const String _nextStepDateFormat = 'EEEE, MMM d';
- HealthStatusBlob({this.code, this.priority, this.nextStep, this.nextStepHtml, this.nextStepDateUtc, this.eventExplanation, this.eventExplanationHtml, this.warning, this.warningHtml, this.reason, this.fcmTopic, this.historyBlob});
+ HealthStatusBlob({this.code, this.priority,
+ this.nextStep, this.nextStepHtml, this.nextStepDateUtc,
+ this.warning, this.warningHtml,
+ this.eventExplanation, this.eventExplanationHtml,
+ this.statusUpdateNotice, this.statusUpdateNoticeHtml,
+ this.statusUpdateReason, this.statusUpdateReasonHtml,
+ this.fcmTopic, this.historyBlob});
factory HealthStatusBlob.fromJson(Map json) {
return (json != null) ? HealthStatusBlob(
@@ -150,11 +160,14 @@ class HealthStatusBlob {
nextStep: json['next_step'],
nextStepHtml: json['next_step_html'],
nextStepDateUtc: healthDateTimeFromString(json['next_step_date']),
- eventExplanation: json['event_explanation'],
- eventExplanationHtml: json['event_explanation_html'],
warning: json['warning'],
warningHtml: json['warning_html'],
- reason: json['reason'],
+ eventExplanation: json['event_explanation'],
+ eventExplanationHtml: json['event_explanation_html'],
+ statusUpdateNotice: json['notice'],
+ statusUpdateNoticeHtml: json['notice_html'],
+ statusUpdateReason: json['reason'],
+ statusUpdateReasonHtml: json['reason_html'],
fcmTopic: json['fcm_topic'],
historyBlob: HealthHistoryBlob.fromJson(json['history_blob']),
) : null;
@@ -167,11 +180,14 @@ class HealthStatusBlob {
'next_step': nextStep,
'next_step_html': nextStepHtml,
'next_step_date': healthDateTimeToString(nextStepDateUtc),
- 'event_explanation': eventExplanation,
- 'event_explanation_html': eventExplanationHtml,
'warning': warning,
'warning_html': warningHtml,
- 'reason': reason,
+ 'event_explanation': eventExplanation,
+ 'event_explanation_html': eventExplanationHtml,
+ 'notice': statusUpdateNotice,
+ 'notice_html': statusUpdateNoticeHtml,
+ 'reason': statusUpdateReason,
+ 'reason_html': statusUpdateReasonHtml,
'fcm_topic': fcmTopic,
'history_blob': historyBlob?.toJson(),
};
@@ -184,11 +200,14 @@ class HealthStatusBlob {
(o.nextStep == nextStep) &&
(o.nextStepHtml == nextStepHtml) &&
(o.nextStepDateUtc == nextStepDateUtc) &&
- (o.eventExplanation == eventExplanation) &&
- (o.eventExplanationHtml == eventExplanationHtml) &&
(o.warning == warning) &&
(o.warningHtml == warningHtml) &&
- (o.reason == reason) &&
+ (o.eventExplanation == eventExplanation) &&
+ (o.eventExplanationHtml == eventExplanationHtml) &&
+ (o.statusUpdateNotice == statusUpdateNotice) &&
+ (o.statusUpdateNoticeHtml == statusUpdateNoticeHtml) &&
+ (o.statusUpdateReason == statusUpdateReason) &&
+ (o.statusUpdateReasonHtml == statusUpdateReasonHtml) &&
DeepCollectionEquality().equals(o.fcmTopic, fcmTopic) &&
(o.historyBlob == historyBlob);
}
@@ -199,14 +218,37 @@ class HealthStatusBlob {
(nextStep?.hashCode ?? 0) ^
(nextStepHtml?.hashCode ?? 0) ^
(nextStepDateUtc?.hashCode ?? 0) ^
- (eventExplanation?.hashCode ?? 0) ^
- (eventExplanationHtml?.hashCode ?? 0) ^
(warning?.hashCode ?? 0) ^
(warningHtml?.hashCode ?? 0) ^
- (reason?.hashCode ?? 0) ^
+ (eventExplanation?.hashCode ?? 0) ^
+ (eventExplanationHtml?.hashCode ?? 0) ^
+ (statusUpdateNotice?.hashCode ?? 0) ^
+ (statusUpdateNoticeHtml?.hashCode ?? 0) ^
+ (statusUpdateReason?.hashCode ?? 0) ^
+ (statusUpdateReasonHtml?.hashCode ?? 0) ^
(DeepCollectionEquality().hash(fcmTopic) ?? 0) ^
(historyBlob?.hashCode ?? 0);
+ factory HealthStatusBlob.fromRuleStatus(HealthRuleStatus ruleStatus, { HealthRulesSet rules, HealthStatusBlob previousStatusBlob, HealthHistoryBlob historyBlob }) {
+ return (ruleStatus != null) ? HealthStatusBlob(
+ code: (ruleStatus.code != null) ? ruleStatus.code : previousStatusBlob?.code,
+ priority: (ruleStatus.priority != null) ? ruleStatus.priority.abs() : previousStatusBlob?.priority,
+ nextStep: ((ruleStatus.nextStep != null) || (ruleStatus.nextStepHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.nextStep) : previousStatusBlob?.nextStep,
+ nextStepHtml: ((ruleStatus.nextStep != null) || (ruleStatus.nextStepHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.nextStepHtml) : previousStatusBlob?.nextStepHtml,
+ nextStepDateUtc: ((ruleStatus.nextStepInterval != null) || (ruleStatus.nextStep != null) || (ruleStatus.nextStepHtml != null) || (ruleStatus.code != null)) ? ruleStatus.nextStepDateUtc : previousStatusBlob?.nextStepDateUtc,
+ eventExplanation: ((ruleStatus.eventExplanation != null) || (ruleStatus.eventExplanationHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.eventExplanation) : previousStatusBlob?.eventExplanation,
+ eventExplanationHtml: ((ruleStatus.eventExplanation != null) || (ruleStatus.eventExplanationHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.eventExplanationHtml) : previousStatusBlob?.eventExplanationHtml,
+ warning: ((ruleStatus.warning != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.warning) : previousStatusBlob?.warning,
+ warningHtml: ((ruleStatus.warningHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.warningHtml) : previousStatusBlob?.warningHtml,
+ statusUpdateNotice: ((ruleStatus.statusUpdateNotice != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.statusUpdateNotice) : previousStatusBlob?.statusUpdateNotice,
+ statusUpdateNoticeHtml: ((ruleStatus.statusUpdateNoticeHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.statusUpdateNoticeHtml) : previousStatusBlob?.statusUpdateNoticeHtml,
+ statusUpdateReason: ((ruleStatus.statusUpdateReason != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.statusUpdateReason) : previousStatusBlob?.statusUpdateReason,
+ statusUpdateReasonHtml: ((ruleStatus.statusUpdateReasonHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.statusUpdateReasonHtml) : previousStatusBlob?.statusUpdateReasonHtml,
+ fcmTopic: ((ruleStatus.fcmTopic != null) || (ruleStatus.code != null)) ? ruleStatus.fcmTopic : previousStatusBlob?.fcmTopic,
+ historyBlob: historyBlob,
+ ) : null;
+ }
+
String get displayNextStep {
return _processMacros(nextStep);
}
@@ -231,6 +273,14 @@ class HealthStatusBlob {
return null;
}
+ String get displayWarning {
+ return _processMacros(warning);
+ }
+
+ String get displayWarningHtml {
+ return _processMacros(warningHtml);
+ }
+
String get displayEventExplanation {
return _processMacros(eventExplanation);
}
@@ -239,16 +289,20 @@ class HealthStatusBlob {
return _processMacros(eventExplanationHtml);
}
- String get displayWarning {
- return _processMacros(warning);
+ String get displayStatusUpdateNotice {
+ return _processMacros(statusUpdateNotice);
}
- String get displayWarningHtml {
- return _processMacros(warningHtml);
+ String get displayStatusUpdateNoticeHtml {
+ return _processMacros(statusUpdateNoticeHtml);
}
- String get displayReason {
- return _processMacros(reason);
+ String get displayStatusUpdateReason {
+ return _processMacros(statusUpdateReason);
+ }
+
+ String get displayStatusUpdateReasonHtml {
+ return _processMacros(statusUpdateReasonHtml);
}
String _processMacros(String value) {
@@ -467,7 +521,7 @@ class HealthHistory implements Comparable {
(this.blob?.providerId == event?.providerId) &&
(this.blob?.testType == event?.blob?.testType) &&
(this.blob?.testResult == event?.blob?.testResult) &&
- (ListEquality().equals(this.blob.extras, event.blob.extras));
+ (DeepCollectionEquality().equals(this.blob.extras, event.blob.extras));
}
else if (event.isVaccine) {
return this.isVaccine &&
@@ -482,8 +536,8 @@ class HealthHistory implements Comparable {
(this.blob?.actionType == event?.blob?.actionType) &&
(DeepCollectionEquality().equals(this.blob?.actionText, event?.blob?.actionText)) &&
(DeepCollectionEquality().equals(this.blob?.actionTitle, event?.blob?.actionTitle)) &&
- (MapEquality().equals(this.blob?.actionParams, event?.blob?.actionParams)) &&
- (ListEquality().equals(this.blob.extras, event.blob.extras));
+ (DeepCollectionEquality().equals(this.blob?.actionParams, event?.blob?.actionParams)) &&
+ (DeepCollectionEquality().equals(this.blob.extras, event.blob.extras));
}
else {
return false;
@@ -678,7 +732,6 @@ class HealthHistoryBlob {
final List extras;
static const String VaccineEffective = "Effective";
- static const String VaccineTaken = "Taken";
HealthHistoryBlob({
this.provider, this.providerId, this.location, this.locationId, this.countyId, this.testType, this.testResult,
@@ -752,7 +805,7 @@ class HealthHistoryBlob {
(o.testType == testType) &&
(o.testResult == testResult) &&
- ListEquality().equals(o.symptoms, symptoms) &&
+ DeepCollectionEquality().equals(o.symptoms, symptoms) &&
(o.traceDuration == traceDuration) &&
(o.traceTEK == traceTEK) &&
@@ -762,9 +815,9 @@ class HealthHistoryBlob {
(o.actionType == actionType) &&
DeepCollectionEquality().equals(o.actionTitle, actionTitle) &&
DeepCollectionEquality().equals(o.actionText, actionText) &&
- MapEquality().equals(o.actionParams, actionParams) &&
+ DeepCollectionEquality().equals(o.actionParams, actionParams) &&
- ListEquality().equals(o.extras, extras);
+ DeepCollectionEquality().equals(o.extras, extras);
}
int get hashCode =>
@@ -776,7 +829,7 @@ class HealthHistoryBlob {
(testType?.hashCode ?? 0) ^
(testResult?.hashCode ?? 0) ^
- ListEquality().hash(symptoms) ^
+ DeepCollectionEquality().hash(symptoms) ^
(traceDuration?.hashCode ?? 0) ^
(traceTEK?.hashCode ?? 0) ^
@@ -786,9 +839,9 @@ class HealthHistoryBlob {
(actionType?.hashCode ?? 0) ^
(DeepCollectionEquality().hash(actionTitle) ?? 0) ^
(DeepCollectionEquality().hash(actionText) ?? 0) ^
- (MapEquality().hash(actionParams) ?? 0) ^
+ (DeepCollectionEquality().hash(actionParams) ?? 0) ^
- (ListEquality().hash(extras) ?? 0);
+ (DeepCollectionEquality().hash(extras) ?? 0);
bool get isTest {
return (testType != null) && (testResult != null);
@@ -810,10 +863,6 @@ class HealthHistoryBlob {
return (vaccine != null) && (vaccine.toLowerCase() == VaccineEffective.toLowerCase());
}
- bool get isVaccineTaken {
- return (vaccine != null) && (vaccine.toLowerCase() == VaccineTaken.toLowerCase());
- }
-
bool get isAction {
return (actionType != null) || (actionTitle != null) || (actionText != null) || (actionParams != null);
}
@@ -1280,7 +1329,7 @@ class HealthUser {
o.consentVaccineInformation == consentVaccineInformation &&
o.consentExposureNotification == consentExposureNotification &&
o.repost == repost &&
- ListEquality().equals(o.accounts, accounts) &&
+ DeepCollectionEquality().equals(o.accounts, accounts) &&
o.encryptedKey == encryptedKey &&
o.encryptedBlob == encryptedBlob;
@@ -1291,7 +1340,7 @@ class HealthUser {
(consentVaccineInformation?.hashCode ?? 0) ^
(consentExposureNotification?.hashCode ?? 0) ^
(repost?.hashCode ?? 0) ^
- ListEquality().hash(accounts) ^
+ DeepCollectionEquality().hash(accounts) ^
(encryptedKey?.hashCode ?? 0) ^
(encryptedBlob?.hashCode ?? 0);
@@ -2537,14 +2586,14 @@ class HealthSymptomsGroup {
(o.name == name) &&
(o.visible == visible) &&
(o.group == group) &&
- ListEquality().equals(o.symptoms, symptoms);
+ DeepCollectionEquality().equals(o.symptoms, symptoms);
int get hashCode =>
(id?.hashCode ?? 0) ^
(name?.hashCode ?? 0) ^
(visible?.hashCode ?? 0) ^
(group?.hashCode ?? 0) ^
- ListEquality().hash(symptoms);
+ DeepCollectionEquality().hash(symptoms);
static Map getCounts(List groups, Set selected) {
Map counts = Map();
@@ -2646,6 +2695,21 @@ class HealthRulesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'tests': tests?.toJson(),
+ 'symptoms': symptoms?.toJson(),
+ 'contact_trace': contactTrace?.toJson(),
+ 'vaccines': vaccines?.toJson(),
+ 'actions': actions?.toJson(),
+ 'defaults': defaults?.toJson(),
+ 'codes': codes?.toJson(),
+ 'statuses': _HealthRuleStatus.mapToJson(statuses),
+ 'intervals': _HealthRuleInterval.mapToJson(intervals),
+ 'strings': strings,
+ };
+ }
+
bool operator ==(o) {
return (o is HealthRulesSet) &&
(o.tests == tests) &&
@@ -2655,8 +2719,8 @@ class HealthRulesSet {
(o.actions == actions) &&
(o.defaults == defaults) &&
(o.codes == codes) &&
- MapEquality().equals(o.statuses, statuses) &&
- MapEquality().equals(o.intervals, intervals) &&
+ DeepCollectionEquality().equals(o.statuses, statuses) &&
+ DeepCollectionEquality().equals(o.intervals, intervals) &&
DeepCollectionEquality().equals(o.strings, strings);
}
@@ -2668,8 +2732,8 @@ class HealthRulesSet {
(actions?.hashCode ?? 0) ^
(defaults?.hashCode ?? 0) ^
(codes?.hashCode ?? 0) ^
- MapEquality().hash(statuses) ^
- MapEquality().hash(intervals) ^
+ DeepCollectionEquality().hash(statuses) ^
+ DeepCollectionEquality().hash(intervals) ^
DeepCollectionEquality().hash(strings);
_HealthRuleInterval _getInterval(String name) {
@@ -2719,6 +2783,12 @@ class HealthDefaultsSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'status': status?.toJson(),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthDefaultsSet) &&
(o.status == status);
@@ -2748,14 +2818,21 @@ class HealthCodesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'list': HealthCodeData.listToJson(_codesList),
+ 'info': _info
+ };
+ }
+
bool operator ==(o) =>
(o is HealthCodesSet) &&
- ListEquality().equals(o._codesList, _codesList) &&
- ListEquality().equals(o._info, _info);
+ DeepCollectionEquality().equals(o._codesList, _codesList) &&
+ DeepCollectionEquality().equals(o._info, _info);
int get hashCode =>
- ListEquality().hash(_codesList) ^
- ListEquality().hash(_info);
+ DeepCollectionEquality().hash(_codesList) ^
+ DeepCollectionEquality().hash(_info);
List get list {
return _codesList;
@@ -2809,6 +2886,18 @@ class HealthCodeData {
) : null;
}
+ Map toJson() {
+ return {
+ 'code': code,
+ 'color': _colorString,
+ 'name': _name,
+ 'description': _description,
+ 'long_description': _longDescription,
+ 'visible': visible,
+ 'reports_exposures': reportsExposures
+ };
+ }
+
bool operator ==(o) =>
(o is HealthCodeData) &&
(o.code == code) &&
@@ -2862,6 +2951,17 @@ class HealthCodeData {
return values;
}
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthCodeData value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
static Map mapFromList(List list) {
Map map;
if (list != null) {
@@ -2890,12 +2990,18 @@ class HealthTestRulesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'rules': HealthTestRule.listToJson(_rules),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthTestRulesSet) &&
- ListEquality().equals(o._rules, _rules);
+ DeepCollectionEquality().equals(o._rules, _rules);
int get hashCode =>
- ListEquality().hash(_rules);
+ DeepCollectionEquality().hash(_rules);
HealthTestRuleResult matchRuleResult({ HealthHistoryBlob blob, HealthRulesSet rules }) {
if ((_rules != null) && (blob != null) && (blob.testType != null) && (blob.testResult != null)) {
@@ -2932,16 +3038,24 @@ class HealthTestRule {
) : null;
}
+ Map toJson() {
+ return {
+ 'test_type': testType,
+ 'category': category,
+ 'results': HealthTestRuleResult.listToJson(results),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthTestRule) &&
(o.testType == testType) &&
(o.category == category) &&
- ListEquality().equals(o.results, results);
+ DeepCollectionEquality().equals(o.results, results);
int get hashCode =>
(testType?.hashCode ?? 0) ^
(category?.hashCode ?? 0) ^
- ListEquality().hash(results);
+ DeepCollectionEquality().hash(results);
static List listFromJson(List json) {
List values;
@@ -2954,6 +3068,17 @@ class HealthTestRule {
}
return values;
}
+
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthTestRule value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
}
///////////////////////////////
@@ -2976,12 +3101,21 @@ class HealthTestRuleResult {
) : null;
}
+ Map toJson() {
+ return {
+ 'result': testResult,
+ 'category': category,
+ 'disclaimer_html': disclaimerHtml,
+ 'status': status?.toJson(),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthTestRuleResult) &&
(o.testResult == testResult) &&
(o.category == category) &&
(o.disclaimerHtml == disclaimerHtml) &&
- (status == status);
+ (o.status == status);
int get hashCode =>
(testResult?.hashCode ?? 0) ^
@@ -3001,6 +3135,17 @@ class HealthTestRuleResult {
return values;
}
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthTestRuleResult value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
static HealthTestRuleResult matchRuleResult(List results, { HealthHistoryBlob blob }) {
if (results != null) {
for (HealthTestRuleResult result in results) {
@@ -3033,14 +3178,21 @@ class HealthSymptomsRulesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'rules': HealthSymptomsRule.listToJson(_rules),
+ 'groups': HealthSymptomsGroup.listToJson(groups),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthSymptomsRulesSet) &&
- ListEquality().equals(o._rules, _rules) &&
- ListEquality().equals(o.groups, groups);
+ DeepCollectionEquality().equals(o._rules, _rules) &&
+ DeepCollectionEquality().equals(o.groups, groups);
int get hashCode =>
- ListEquality().hash(_rules) ^
- ListEquality().hash(groups);
+ DeepCollectionEquality().hash(_rules) ^
+ DeepCollectionEquality().hash(groups);
HealthSymptomsRule matchRule({ HealthHistoryBlob blob, HealthRulesSet rules }) {
if ((_rules != null) && (groups != null) && (blob?.symptomsIds != null)) {
@@ -3071,13 +3223,20 @@ class HealthSymptomsRule {
) : null;
}
+ Map toJson() {
+ return {
+ 'counts': _HealthRuleInterval.mapToJson(counts),
+ 'status': status?.toJson()
+ };
+ }
+
bool operator ==(o) =>
(o is HealthSymptomsRule) &&
- MapEquality().equals(o.counts, counts) &&
+ DeepCollectionEquality().equals(o.counts, counts) &&
(o.status == status);
int get hashCode =>
- MapEquality().hash(counts) ^
+ DeepCollectionEquality().hash(counts) ^
(status?.hashCode ?? 0);
static List listFromJson(List json) {
@@ -3092,6 +3251,17 @@ class HealthSymptomsRule {
return values;
}
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthSymptomsRule value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
bool _matchCounts(Map testCounts, { HealthRulesSet rules }) {
if (this.counts != null) {
for (String groupName in this.counts.keys) {
@@ -3121,12 +3291,18 @@ class HealthContactTraceRulesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'rules': HealthContactTraceRule.listToJson(_rules),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthContactTraceRulesSet) &&
- ListEquality().equals(o._rules, _rules);
+ DeepCollectionEquality().equals(o._rules, _rules);
int get hashCode =>
- ListEquality().hash(_rules);
+ DeepCollectionEquality().hash(_rules);
HealthContactTraceRule matchRule({ HealthHistoryBlob blob, HealthRulesSet rules }) {
@@ -3166,6 +3342,13 @@ class HealthContactTraceRule {
) : null;
}
+ Map toJson() {
+ return {
+ 'duration': duration?.toJson(),
+ 'status': status?.toJson(),
+ };
+ }
+
static List listFromJson(List json) {
List values;
if (json != null) {
@@ -3178,6 +3361,17 @@ class HealthContactTraceRule {
return values;
}
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthContactTraceRule value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
bool _matchBlob(HealthHistoryBlob blob, { HealthRulesSet rules }) {
return (duration != null) && duration.match(blob?.traceDurationInMinutes, rules: rules);
}
@@ -3197,12 +3391,18 @@ class HealthVaccineRulesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'rules': HealthVaccineRule.listToJson(_rules),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthVaccineRulesSet) &&
- ListEquality().equals(o._rules, _rules);
+ DeepCollectionEquality().equals(o._rules, _rules);
int get hashCode =>
- ListEquality().hash(_rules);
+ DeepCollectionEquality().hash(_rules);
HealthVaccineRule matchRule({ HealthHistoryBlob blob, HealthRulesSet rules }) {
if (_rules != null) {
@@ -3232,6 +3432,13 @@ class HealthVaccineRule {
) : null;
}
+ Map toJson() {
+ return {
+ 'vaccine': vaccine,
+ 'status': status?.toJson(),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthVaccineRule) &&
(o.vaccine == vaccine) &&
@@ -3253,6 +3460,17 @@ class HealthVaccineRule {
return values;
}
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthVaccineRule value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
bool _matchBlob(HealthHistoryBlob blob, {HealthRulesSet rules}) {
return (vaccine != null) && (vaccine.toLowerCase() == blob?.vaccine?.toLowerCase());
}
@@ -3272,12 +3490,18 @@ class HealthActionRulesSet {
) : null;
}
+ Map toJson() {
+ return {
+ 'rules': HealthActionRule.listToJson(_rules),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthActionRulesSet) &&
- ListEquality().equals(o._rules, _rules);
+ DeepCollectionEquality().equals(o._rules, _rules);
int get hashCode =>
- ListEquality().hash(_rules);
+ DeepCollectionEquality().hash(_rules);
HealthActionRule matchRule({ HealthHistoryBlob blob, HealthRulesSet rules }) {
if (_rules != null) {
@@ -3307,6 +3531,13 @@ class HealthActionRule {
) : null;
}
+ Map toJson() {
+ return {
+ 'type': type,
+ 'status': status?.toJson()
+ };
+ }
+
bool operator ==(o) =>
(o is HealthActionRule) &&
(o.type == type) &&
@@ -3328,6 +3559,17 @@ class HealthActionRule {
return values;
}
+ static List listToJson(List values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (HealthActionRule value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
bool _matchBlob(HealthHistoryBlob blob, {HealthRulesSet rules}) {
return (type != null) && (type.toLowerCase() == blob?.actionType?.toLowerCase());
}
@@ -3357,6 +3599,8 @@ abstract class _HealthRuleStatus {
return null;
}
+ dynamic toJson();
+
static Map mapFromJson(Map json) {
Map result;
if (json != null) {
@@ -3369,6 +3613,17 @@ abstract class _HealthRuleStatus {
return result;
}
+ static Map mapToJson(Map values) {
+ Map json;
+ if (values != null) {
+ json = Map();
+ values.forEach((key, value) {
+ json[key] = value?.toJson();
+ });
+ }
+ return json;
+ }
+
HealthRuleStatus eval({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params });
}
@@ -3385,52 +3640,85 @@ class HealthRuleStatus extends _HealthRuleStatus {
final _HealthRuleInterval nextStepInterval;
final DateTime nextStepDateUtc;
+ final dynamic warning;
+ final dynamic warningHtml;
+
final dynamic eventExplanation;
final dynamic eventExplanationHtml;
- final dynamic warning;
- final dynamic warningHtml;
+ final dynamic statusUpdateNotice;
+ final dynamic statusUpdateNoticeHtml;
- final dynamic reason;
+ final dynamic statusUpdateReason;
+ final dynamic statusUpdateReasonHtml;
final dynamic fcmTopic;
HealthRuleStatus({this.code, this.priority,
this.nextStep, this.nextStepHtml, this.nextStepInterval, this.nextStepDateUtc,
+ this.warning, this.warningHtml,
this.eventExplanation, this.eventExplanationHtml,
- this.warning, this.warningHtml, this.reason, this.fcmTopic });
+ this.statusUpdateNotice, this.statusUpdateNoticeHtml,
+ this.statusUpdateReason, this.statusUpdateReasonHtml,
+ this.fcmTopic });
factory HealthRuleStatus.fromJson(Map json) {
return (json != null) ? HealthRuleStatus(
- code: json['code'],
- priority: json['priority'],
- nextStep: json['next_step'],
- nextStepHtml: json['next_step_html'],
- nextStepInterval: _HealthRuleInterval.fromJson(json['next_step_interval']),
- eventExplanation: json['event_explanation'],
- eventExplanationHtml: json['event_explanation_html'],
- warning: json['warning'],
- warningHtml: json['warning_html'],
- reason: json['reason'],
- fcmTopic: json['fcm_topic']
+ code: json['code'],
+ priority: json['priority'],
+ nextStep: json['next_step'],
+ nextStepHtml: json['next_step_html'],
+ nextStepInterval: _HealthRuleInterval.fromJson(json['next_step_interval']),
+ warning: json['warning'],
+ warningHtml: json['warning_html'],
+ eventExplanation: json['event_explanation'],
+ eventExplanationHtml: json['event_explanation_html'],
+ statusUpdateNotice: json['notice'],
+ statusUpdateNoticeHtml: json['notice_html'],
+ statusUpdateReason: json['reason'],
+ statusUpdateReasonHtml: json['reason_html'],
+ fcmTopic: json['fcm_topic']
) : null;
}
+ @override
+ dynamic toJson() {
+ return {
+ 'code': code,
+ 'priority': priority,
+ 'next_step': nextStep,
+ 'next_step_html': nextStepHtml,
+ 'next_step_interval': nextStepInterval?.toJson(),
+ 'warning': warning,
+ 'warning_html': warningHtml,
+ 'event_explanation': eventExplanation,
+ 'event_explanation_html': eventExplanationHtml,
+ 'notice': statusUpdateNotice,
+ 'notice_html': statusUpdateNoticeHtml,
+ 'reason': statusUpdateReason,
+ 'reason_html': statusUpdateReasonHtml,
+ 'fcm_topic': fcmTopic,
+ };
+ }
+
factory HealthRuleStatus.fromStatus(HealthRuleStatus status, { DateTime nextStepDateUtc, }) {
return (status != null) ? HealthRuleStatus(
- code: status.code,
- priority: status.priority,
- nextStep: status.nextStep,
- nextStepHtml: status.nextStepHtml,
- nextStepInterval: status.nextStepInterval,
- nextStepDateUtc: nextStepDateUtc ?? status.nextStepDateUtc,
- eventExplanation: status.eventExplanation,
- eventExplanationHtml: status.eventExplanationHtml,
- warning: status.warning,
- warningHtml: status.warningHtml,
- reason: status.reason,
- fcmTopic: status.fcmTopic,
+ code: status.code,
+ priority: status.priority,
+ nextStep: status.nextStep,
+ nextStepHtml: status.nextStepHtml,
+ nextStepInterval: status.nextStepInterval,
+ nextStepDateUtc: nextStepDateUtc ?? status.nextStepDateUtc,
+ warning: status.warning,
+ warningHtml: status.warningHtml,
+ eventExplanation: status.eventExplanation,
+ eventExplanationHtml: status.eventExplanationHtml,
+ statusUpdateNotice: status.statusUpdateNotice,
+ statusUpdateNoticeHtml: status.statusUpdateNoticeHtml,
+ statusUpdateReason: status.statusUpdateReason,
+ statusUpdateReasonHtml: status.statusUpdateReasonHtml,
+ fcmTopic: status.fcmTopic,
) : null;
}
@@ -3444,13 +3732,17 @@ class HealthRuleStatus extends _HealthRuleStatus {
(o.nextStepInterval == nextStepInterval) &&
(o.nextStepDateUtc == nextStepDateUtc) &&
+ (o.warning == warning) &&
+ (o.warningHtml == warningHtml) &&
+
(o.eventExplanation == eventExplanation) &&
(o.eventExplanationHtml == eventExplanationHtml) &&
- (o.warning == warning) &&
- (o.warningHtml == warningHtml) &&
+ (o.statusUpdateNotice == statusUpdateNotice) &&
+ (o.statusUpdateNoticeHtml == statusUpdateNoticeHtml) &&
- (o.reason == reason) &&
+ (o.statusUpdateReason == statusUpdateReason) &&
+ (o.statusUpdateReasonHtml == statusUpdateReasonHtml) &&
(o.fcmTopic == fcmTopic);
@@ -3463,13 +3755,17 @@ class HealthRuleStatus extends _HealthRuleStatus {
(nextStepInterval?.hashCode ?? 0) ^
(nextStepDateUtc?.hashCode ?? 0) ^
+ (warning?.hashCode ?? 0) ^
+ (warningHtml?.hashCode ?? 0) ^
+
(eventExplanation?.hashCode ?? 0) ^
(eventExplanationHtml?.hashCode ?? 0) ^
- (warning?.hashCode ?? 0) ^
- (warningHtml?.hashCode ?? 0) ^
+ (statusUpdateNotice?.hashCode ?? 0) ^
+ (statusUpdateNoticeHtml?.hashCode ?? 0) ^
- (reason?.hashCode ?? 0) ^
+ (statusUpdateReason?.hashCode ?? 0) ^
+ (statusUpdateReasonHtml?.hashCode ?? 0) ^
(fcmTopic?.hashCode ?? 0);
@@ -3505,6 +3801,11 @@ class HealthRuleReferenceStatus extends _HealthRuleStatus {
) : null;
}
+ @override
+ dynamic toJson() {
+ return reference;
+ }
+
bool operator ==(o) =>
(o is HealthRuleReferenceStatus) &&
(o.reference == reference);
@@ -3539,6 +3840,16 @@ class HealthRuleConditionalStatus extends _HealthRuleStatus with HealthRuleCondi
) : null;
}
+ @override
+ dynamic toJson() {
+ return {
+ 'condition': condition,
+ 'params': conditionParams,
+ 'success': successStatus?.toJson(),
+ 'fail': failStatus?.toJson(),
+ };
+ }
+
static bool isJsonCompatible(dynamic json) {
return (json is Map) && (json['condition'] is String);
}
@@ -3546,13 +3857,13 @@ class HealthRuleConditionalStatus extends _HealthRuleStatus with HealthRuleCondi
bool operator ==(o) =>
(o is HealthRuleConditionalStatus) &&
(o.condition == condition) &&
- (MapEquality().equals(o.conditionParams, conditionParams)) &&
+ (DeepCollectionEquality().equals(o.conditionParams, conditionParams)) &&
(o.successStatus == successStatus) &&
(o.failStatus == failStatus);
int get hashCode =>
(condition?.hashCode ?? 0) ^
- (MapEquality().hash(conditionParams)) ^
+ (DeepCollectionEquality().hash(conditionParams)) ^
(successStatus?.hashCode ?? 0) ^
(failStatus?.hashCode ?? 0);
@@ -3577,6 +3888,10 @@ abstract class _HealthRuleInterval {
else if (json is String) {
return HealthRuleIntervalReference.fromJson(json);
}
+ else if (json is List) {
+ try { return HealthRuleIntervalSet.fromJson(json.cast()); }
+ catch (e) { print(e?.toString()); }
+ }
else if (json is Map) {
if (HealthRuleIntervalCondition.isJsonCompatible(json)) {
try { return HealthRuleIntervalCondition.fromJson(json.cast()); }
@@ -3590,7 +3905,9 @@ abstract class _HealthRuleInterval {
return null;
}
- bool match(int value, { List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params });
+ dynamic toJson();
+
+ bool match(int value, { DateTime orgDate, List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params });
int value({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params });
bool valid({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params });
int scope({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params });
@@ -3607,6 +3924,55 @@ abstract class _HealthRuleInterval {
}
return result;
}
+
+ static Map mapToJson(Map values) {
+ Map json;
+ if (values != null) {
+ json = Map();
+ values.forEach((key, value) {
+ json[key] = value?.toJson();
+ });
+ }
+ return json;
+ }
+
+ static List<_HealthRuleInterval> listFromJson(List json) {
+ List<_HealthRuleInterval> values;
+ if (json != null) {
+ values = <_HealthRuleInterval>[];
+ for (dynamic entry in json) {
+ try { values.add(_HealthRuleInterval.fromJson(entry)); }
+ catch(e) { print(e?.toString()); }
+ }
+ }
+ return values;
+ }
+
+ static List listToJson(List<_HealthRuleInterval> values) {
+ List json;
+ if (values != null) {
+ json = [];
+ for (_HealthRuleInterval value in values) {
+ json.add(value?.toJson());
+ }
+ }
+ return json;
+ }
+
+ static int applyWeekdayExtent(_HealthRuleInterval weekdayExtent, DateTime orgDate, int value, int step, { List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params } ) {
+ if ((weekdayExtent != null) && (orgDate != null) && (value != null) && (step != null)) {
+ //DateTime dateExt = orgDate.add(Duration(days: value + step));
+ DateTime dateExt = DateTime(orgDate.year, orgDate.month, orgDate.day + value + step, orgDate.hour, orgDate.minute, orgDate.second);
+ while (weekdayExtent.match(dateExt.weekday, orgDate: orgDate, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
+ value += step;
+ // dateExt = dateExt.add(Duration(days: step));
+ dateExt = DateTime(dateExt.year, dateExt.month, dateExt.day + step, orgDate.hour, orgDate.minute, orgDate.second);
+ }
+ return value;
+ }
+ return null;
+ }
+
}
enum HealthRuleIntervalOrigin { historyDate, referenceDate }
@@ -3624,6 +3990,11 @@ class HealthRuleIntervalValue extends _HealthRuleInterval {
return (json is int) ? HealthRuleIntervalValue(value: json) : null;
}
+ @override
+ dynamic toJson() {
+ return _value;
+ }
+
bool operator ==(o) =>
(o is HealthRuleIntervalValue) &&
(o._value == _value);
@@ -3631,7 +4002,7 @@ class HealthRuleIntervalValue extends _HealthRuleInterval {
int get hashCode =>
(_value?.hashCode ?? 0);
- @override bool match(int value, { List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {return (_value == value); }
+ @override bool match(int value, { DateTime orgDate, List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {return (_value == value); }
@override int value({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return _value; }
@override bool valid({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return (_value != null); }
@override int scope({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return null; }
@@ -3655,14 +4026,18 @@ class HealthRuleInterval extends _HealthRuleInterval {
final int _scope;
final bool _current;
final HealthRuleIntervalOrigin _origin;
+ final _HealthRuleInterval _minWeekdaysExtent;
+ final _HealthRuleInterval _maxWeekdaysExtent;
- HealthRuleInterval({_HealthRuleInterval min, _HealthRuleInterval max, _HealthRuleInterval value, int scope, bool current, HealthRuleIntervalOrigin origin}) :
+ HealthRuleInterval({_HealthRuleInterval min, _HealthRuleInterval max, _HealthRuleInterval value, int scope, bool current, HealthRuleIntervalOrigin origin, _HealthRuleInterval minWeekdaysExtent, _HealthRuleInterval maxWeekdaysExtent }) :
_min = min,
_max = max,
_value = value,
_scope = scope,
_current = current,
- _origin = origin;
+ _origin = origin,
+ _minWeekdaysExtent = minWeekdaysExtent,
+ _maxWeekdaysExtent = maxWeekdaysExtent;
factory HealthRuleInterval.fromJson(Map json) {
return (json != null) ? HealthRuleInterval(
@@ -3672,9 +4047,25 @@ class HealthRuleInterval extends _HealthRuleInterval {
scope: _scopeFromJson(json['scope']),
current: json['current'],
origin: _originFromJson(json['origin']),
+ minWeekdaysExtent: _HealthRuleInterval.fromJson(json['min-weekdays-extent']),
+ maxWeekdaysExtent: _HealthRuleInterval.fromJson(json['max-weekdays-extent']),
) : null;
}
+ @override
+ dynamic toJson() {
+ return {
+ 'min': _min?.toJson(),
+ 'max': _max?.toJson(),
+ 'value': _value?.toJson(),
+ 'scope': _scopeToJson(_scope),
+ 'current': _current,
+ 'origin': _originToJson(_origin),
+ 'min-weekdays-extent': _minWeekdaysExtent?.toJson(),
+ 'max-weekdays-extent': _maxWeekdaysExtent?.toJson(),
+ };
+ }
+
bool operator ==(o) =>
(o is HealthRuleInterval) &&
(o._min == _min) &&
@@ -3682,7 +4073,9 @@ class HealthRuleInterval extends _HealthRuleInterval {
(o._value == _value) &&
(o._scope == _scope) &&
(o._current == _current) &&
- (o._origin == _origin);
+ (o._origin == _origin) &&
+ (o._minWeekdaysExtent == _minWeekdaysExtent) &&
+ (o._maxWeekdaysExtent == _maxWeekdaysExtent);
int get hashCode =>
(_min?.hashCode ?? 0) ^
@@ -3690,25 +4083,39 @@ class HealthRuleInterval extends _HealthRuleInterval {
(_value?.hashCode ?? 0) ^
(_scope?.hashCode ?? 0) ^
(_current?.hashCode ?? 0) ^
- (_origin?.hashCode ?? 0);
+ (_origin?.hashCode ?? 0) ^
+ (_minWeekdaysExtent?.hashCode ?? 0) ^
+ (_maxWeekdaysExtent?.hashCode ?? 0);
@override
- bool match(int value, { List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
+ bool match(int value, { DateTime orgDate, List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
if (value != null) {
if (_min != null) {
int minValue = _min.value(history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params);
+ minValue = _HealthRuleInterval.applyWeekdayExtent(_minWeekdaysExtent, orgDate, minValue, -1, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params) ?? minValue;
if ((minValue == null) || (minValue > value)) {
return false;
}
}
if (_max != null) {
int maxValue = _max.value(history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params);
+ maxValue = _HealthRuleInterval.applyWeekdayExtent(_maxWeekdaysExtent, orgDate, maxValue, 1, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params) ?? maxValue;
if ((maxValue == null) || (maxValue < value)) {
return false;
}
}
if (_value != null) {
int valueValue = _value.value(history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params);
+
+ int minValue = _HealthRuleInterval.applyWeekdayExtent(_minWeekdaysExtent, orgDate, valueValue, -1, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params);
+ if ((minValue != null) && (minValue > value)) {
+ return false;
+ }
+ int maxValue = _HealthRuleInterval.applyWeekdayExtent(_maxWeekdaysExtent, orgDate, valueValue, 1, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params);
+ if ((maxValue != null) && (maxValue < value)) {
+ return false;
+ }
+
if ((valueValue == null) || (valueValue != value)) {
return false;
}
@@ -3755,6 +4162,22 @@ class HealthRuleInterval extends _HealthRuleInterval {
return null;
}
+ static String _scopeToJson(int value) {
+ if (value == FutureScope) {
+ return 'future';
+ }
+ else if (value == FutureAndCurrentScope) {
+ return 'future-and-current';
+ }
+ else if (value == PastScope) {
+ return 'past';
+ }
+ else if (value == PastAndCurrentScope) {
+ return 'past-and-current';
+ }
+ return null;
+ }
+
static HealthRuleIntervalOrigin _originFromJson(dynamic value) {
if (value == 'historyDate') {
return HealthRuleIntervalOrigin.historyDate;
@@ -3766,6 +4189,70 @@ class HealthRuleInterval extends _HealthRuleInterval {
return null;
}
}
+
+ static String _originToJson(HealthRuleIntervalOrigin value) {
+ if (value == HealthRuleIntervalOrigin.historyDate) {
+ return 'historyDate';
+ }
+ else if (value == HealthRuleIntervalOrigin.referenceDate) {
+ return 'referenceDate';
+ }
+ else {
+ return null;
+ }
+ }
+}
+
+///////////////////////////////
+// HealthRuleIntervalSet
+
+class HealthRuleIntervalSet extends _HealthRuleInterval {
+ List<_HealthRuleInterval> _entries;
+
+ HealthRuleIntervalSet({List<_HealthRuleInterval> entries}) :
+ _entries = entries;
+
+ factory HealthRuleIntervalSet.fromJson(List json) {
+ List<_HealthRuleInterval> entries = _HealthRuleInterval.listFromJson(json);
+ return (entries != null) ? HealthRuleIntervalSet(entries: entries) : null;
+ }
+
+ @override
+ dynamic toJson() {
+ return _HealthRuleInterval.listToJson(_entries);
+ }
+
+ bool operator ==(o) =>
+ (o is HealthRuleIntervalSet) &&
+ DeepCollectionEquality().equals(o._entries, _entries);
+
+ int get hashCode =>
+ (DeepCollectionEquality().hash(_entries) ?? 0);
+
+ @override
+ bool match(int value, { DateTime orgDate, List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
+ for (_HealthRuleInterval entry in _entries) {
+ if (entry.match(value, orgDate: orgDate, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @override
+ bool valid({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
+ for (_HealthRuleInterval entry in _entries) {
+ if (!entry.valid(history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
+ return false;
+ }
+ }
+ return 0 < _entries.length;
+ }
+
+ @override int value({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return null; }
+ @override int scope({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return null; }
+ @override bool current({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return null; }
+ @override HealthRuleIntervalOrigin origin({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return null; }
}
///////////////////////////////
@@ -3781,6 +4268,11 @@ class HealthRuleIntervalReference extends _HealthRuleInterval {
return (json is String) ? HealthRuleIntervalReference(reference: json) : null;
}
+ @override
+ dynamic toJson() {
+ return _reference;
+ }
+
bool operator ==(o) =>
(o is HealthRuleIntervalReference) &&
(o._reference == _reference);
@@ -3794,8 +4286,8 @@ class HealthRuleIntervalReference extends _HealthRuleInterval {
}
@override
- bool match(int value, { List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
- return _referenceInterval(rules: rules, params: params)?.match(value, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params) ?? false;
+ bool match(int value, { DateTime orgDate, List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
+ return _referenceInterval(rules: rules, params: params)?.match(value, orgDate: orgDate, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params) ?? false;
}
@override bool valid({ List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) { return _referenceInterval(rules: rules, params: params)?.valid(history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params) ?? false; }
@@ -3825,6 +4317,16 @@ class HealthRuleIntervalCondition extends _HealthRuleInterval with HealthRuleCon
) : null;
}
+ @override
+ dynamic toJson() {
+ return {
+ 'condition': condition,
+ 'params': conditionParams,
+ 'success': successInterval?.toJson(),
+ 'fail': failInterval?.toJson(),
+ };
+ }
+
static bool isJsonCompatible(dynamic json) {
return (json is Map) && (json['condition'] is String);
}
@@ -3832,21 +4334,21 @@ class HealthRuleIntervalCondition extends _HealthRuleInterval with HealthRuleCon
bool operator ==(o) =>
(o is HealthRuleIntervalCondition) &&
(o.condition == condition) &&
- (MapEquality().equals(o.conditionParams, conditionParams)) &&
+ (DeepCollectionEquality().equals(o.conditionParams, conditionParams)) &&
(o.successInterval == successInterval) &&
(o.failInterval == failInterval);
int get hashCode =>
(condition?.hashCode ?? 0) ^
- (MapEquality().hash(conditionParams)) ^
+ (DeepCollectionEquality().hash(conditionParams)) ^
(successInterval?.hashCode ?? 0) ^
(failInterval?.hashCode ?? 0);
@override
- bool match(int value, { List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
+ bool match(int value, { DateTime orgDate, List history, int historyIndex, int referenceIndex, HealthRulesSet rules, Map params }) {
HealthRuleConditionResult conditionResult = evalCondition(history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params);
_HealthRuleInterval interval = (conditionResult?.result != null) ? (conditionResult.result ? successInterval : failInterval) : null;
- return interval?.match(value, history: history, historyIndex: historyIndex, referenceIndex: conditionResult?.referenceIndex ?? referenceIndex, rules: rules, params: params) ?? false;
+ return interval?.match(value, orgDate: orgDate, history: history, historyIndex: historyIndex, referenceIndex: conditionResult?.referenceIndex ?? referenceIndex, rules: rules, params: params) ?? false;
}
@override
@@ -3996,7 +4498,7 @@ abstract class HealthRuleCondition {
//#572 Building access calculation issue
//int difference = entryDateMidnightLocal.difference(originDateMidnightLocal).inDays;
int difference = AppDateTime.midnightsDifferenceInDays(originDateMidnightLocal, entryDateMidnightLocal);
- if (interval.match(difference, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
+ if (interval.match(difference, orgDate: originDateMidnightLocal, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
// check filters before returning successfull match
if (_matchValue(historyType, HealthHistoryType.test)) {
@@ -4047,7 +4549,7 @@ abstract class HealthRuleCondition {
//#572 Building access calculation issue
//int difference = AppDateTime.todayMidnightLocal.difference(originDateMidnightLocal).inDays;
int difference = AppDateTime.midnightsDifferenceInDays(originDateMidnightLocal, AppDateTime.todayMidnightLocal);
- if (currentInterval.match(difference, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
+ if (currentInterval.match(difference, orgDate: originDateMidnightLocal, history: history, historyIndex: historyIndex, referenceIndex: referenceIndex, rules: rules, params: params)) {
return true;
}
}
diff --git a/lib/service/Config.dart b/lib/service/Config.dart
index 2d4b0be4..5ea61268 100644
--- a/lib/service/Config.dart
+++ b/lib/service/Config.dart
@@ -41,6 +41,7 @@ class Config with Service implements NotificationsListener {
static const String notifyUpgradeRequired = "edu.illinois.rokwire.config.upgrade.required";
static const String notifyUpgradeAvailable = "edu.illinois.rokwire.config.upgrade.available";
+ static const String notifyOnboardingRequired = "edu.illinois.rokwire.config.onboarding.required";
static const String notifyConfigChanged = "edu.illinois.rokwire.config.changed";
Map _config;
@@ -94,6 +95,7 @@ class Config with Service implements NotificationsListener {
if (_config != null) {
_checkUpgrade();
+ _checkOnboarding();
_updateFromNet();
}
else if (Organizations().organization != null) {
@@ -105,6 +107,7 @@ class Config with Service implements NotificationsListener {
NotificationService().notify(notifyConfigChanged, null);
_checkUpgrade();
+ _checkOnboarding();
}
}
else {
@@ -164,7 +167,7 @@ class Config with Service implements NotificationsListener {
for (int index = jsonList.length - 1; index >= 0; index--) {
Map cfg = jsonList[index];
- if (AppVersion.compareVersions(cfg['mobileAppVersion'], _packageInfo.version) <= 0) {
+ if (AppVersion.compareVersions(cfg['mobileAppVersion'], appVersion) <= 0) {
_decodeSecretKeys(cfg);
return cfg;
}
@@ -196,6 +199,7 @@ class Config with Service implements NotificationsListener {
NotificationService().notify(notifyConfigChanged, null);
_checkUpgrade();
+ _checkOnboarding();
}
});
}
@@ -239,7 +243,7 @@ class Config with Service implements NotificationsListener {
String get upgradeRequiredVersion {
dynamic requiredVersion = _upgradeStringEntry('required_version');
- if ((requiredVersion is String) && (AppVersion.compareVersions(_packageInfo.version, requiredVersion) < 0)) {
+ if ((requiredVersion is String) && (AppVersion.compareVersions(appVersion, requiredVersion) < 0)) {
return requiredVersion;
}
return null;
@@ -248,7 +252,7 @@ class Config with Service implements NotificationsListener {
String get upgradeAvailableVersion {
dynamic availableVersion = _upgradeStringEntry('available_version');
bool upgradeAvailable = (availableVersion is String) &&
- (AppVersion.compareVersions(_packageInfo.version, availableVersion) < 0) &&
+ (AppVersion.compareVersions(appVersion, availableVersion) < 0) &&
!Storage().reportedUpgradeVersions.contains(availableVersion) &&
!_reportedUpgradeVersions.contains(availableVersion);
return upgradeAvailable ? availableVersion : null;
@@ -291,6 +295,23 @@ class Config with Service implements NotificationsListener {
}
}
+ // Onboarding
+
+ String get onboardingRequiredVersion {
+ dynamic requiredVersion = onboardingInfo['required_version'];
+ if ((requiredVersion is String) && (AppVersion.compareVersions(requiredVersion, appVersion) <= 0)) {
+ return requiredVersion;
+ }
+ return null;
+ }
+
+ void _checkOnboarding() {
+ String value;
+ if ((value = this.onboardingRequiredVersion) != null) {
+ NotificationService().notify(notifyOnboardingRequired, value);
+ }
+ }
+
// Assets cache path
Directory get appDocumentsDir {
@@ -333,12 +354,12 @@ class Config with Service implements NotificationsListener {
Map get secretOsf { return secretKeys['osf'] ?? {}; }
Map get secretHealth { return secretKeys['health'] ?? {}; }
- Map get upgradeInfo { return (_config != null) ? (_config['upgrade'] ?? {}) : {}; }
-
Map get settings { return (_config != null) ? (_config['settings'] ?? {}) : {}; }
+ Map get upgradeInfo { return (_config != null) ? (_config['upgrade'] ?? {}) : {}; }
+ Map get onboardingInfo { return (_config != null) ? (_config['onboarding'] ?? {}) : {}; }
String get assetsUrl { return otherUniversityServices['assets_url']; } // "https://rokwire-assets.s3.us-east-2.amazonaws.com"
- String get feedbackUrl { return otherUniversityServices['feedback_url']; } // "https://forms.illinois.edu/sec/1971889"
+ String get getHelpUrl { return otherUniversityServices['get_help_url']; } // "https://forms.illinois.edu/sec/4961936"
String get iCardUrl { return otherUniversityServices['icard_url']; } // "https://www.icard.uillinois.edu/rest/rw/rwIDData/rwCardInfo"
String get privacyPolicyUrl { return otherUniversityServices['privacy_policy_url']; } // "https://www.vpaa.uillinois.edu/resources/web_privacy"
String get exposureLogUrl { return otherUniversityServices['exposure_log_url']; } // "http://ec2-18-191-37-235.us-east-2.compute.amazonaws.com:8003/PostSessionData"
@@ -363,6 +384,7 @@ class Config with Service implements NotificationsListener {
String get imagesServiceUrl { return platformBuildingBlocks['images_service_url']; } // "https://api-dev.rokwire.illinois.edu/images-service";
String get osfBaseUrl { return thirdPartyServices['osf_base_url']; } // "https://ssproxy.osfhealthcare.org/fhir-proxy"
+ String get vaccinationAppointUrl { return thirdPartyServices['vaccination_appointment_url']; } // "https://ssproxy.osfhealthcare.org/fhir-proxy"
String get rokwireApiKey { return secretRokwire['api_key']; }
@@ -376,8 +398,8 @@ class Config with Service implements NotificationsListener {
int get refreshTimeout { return kReleaseMode ? (settings['refreshTimeout'] ?? 0) : 0; }
- bool get residentRoleEnabled { return false; }
- bool get capitolStaffRoleEnabled { return (settings['roleCapitolStaffEnabled'] == true); }
+ bool get residentRoleEnabled { return false; }
+ bool get capitolStaffRoleEnabled { return (settings['roleCapitolStaffEnabled'] == true); }
}
diff --git a/lib/service/Health.dart b/lib/service/Health.dart
index 9a877a2b..317b5812 100644
--- a/lib/service/Health.dart
+++ b/lib/service/Health.dart
@@ -262,6 +262,8 @@ class Health with Service implements NotificationsListener {
}
Future _refreshInternal(_RefreshOptions options) async {
+ //Log.d("Health._refreshInternal($options)");
+
_refreshOptions = options;
NotificationService().notify(notifyRefreshing);
@@ -592,6 +594,7 @@ class Health with Service implements NotificationsListener {
Future setUserPrivateKey(PrivateKey privateKey) async {
if (await _saveUserPrivateKey(privateKey)) {
_userPrivateKey = privateKey;
+ _notify(notifyUserUpdated);
_refresh(_RefreshOptions.fromList([_RefreshOption.history]));
return true;
}
@@ -694,6 +697,7 @@ class Health with Service implements NotificationsListener {
}
Future _loadUserTestMonitorInterval() async {
+//TMP: return 8;
if (this._isUserAuthenticated && (Config().healthUrl != null)) {
String url = "${Config().healthUrl}/covid19/uin-override";
Response response = await Network().get(url, auth: Network.HealthUserAuth);
@@ -825,20 +829,9 @@ class Health with Service implements NotificationsListener {
HealthStatus status = HealthStatus(
dateUtc: null,
- blob: HealthStatusBlob(
- code: defaultStatus.code,
- priority: defaultStatus.priority,
- nextStep: rules.localeString(defaultStatus.nextStep),
- nextStepHtml: rules.localeString(defaultStatus.nextStepHtml),
- nextStepDateUtc: null,
- eventExplanation: rules.localeString(defaultStatus.eventExplanation),
- eventExplanationHtml: rules.localeString(defaultStatus.eventExplanationHtml),
- warning: rules.localeString(defaultStatus.warning),
- warningHtml: rules.localeString(defaultStatus.warningHtml),
- reason: rules.localeString(defaultStatus.reason),
- fcmTopic: defaultStatus.fcmTopic,
- historyBlob: null,
- ),
+ blob: HealthStatusBlob.fromRuleStatus(defaultStatus,
+ rules: rules,
+ )
);
// Start from older
@@ -873,18 +866,9 @@ class Health with Service implements NotificationsListener {
if ((ruleStatus != null) && ruleStatus.canUpdateStatus(blob: status.blob)) {
status = HealthStatus(
dateUtc: historyEntry.dateUtc,
- blob: HealthStatusBlob(
- code: (ruleStatus.code != null) ? ruleStatus.code : status.blob.code,
- priority: (ruleStatus.priority != null) ? ruleStatus.priority.abs() : status.blob.priority,
- nextStep: ((ruleStatus.nextStep != null) || (ruleStatus.nextStepHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.nextStep) : status.blob.nextStep,
- nextStepHtml: ((ruleStatus.nextStep != null) || (ruleStatus.nextStepHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.nextStepHtml) : status.blob.nextStepHtml,
- nextStepDateUtc: ((ruleStatus.nextStepInterval != null) || (ruleStatus.nextStep != null) || (ruleStatus.nextStepHtml != null) || (ruleStatus.code != null)) ? ruleStatus.nextStepDateUtc : status.blob.nextStepDateUtc,
- eventExplanation: ((ruleStatus.eventExplanation != null) || (ruleStatus.eventExplanationHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.eventExplanation) : status.blob.eventExplanation,
- eventExplanationHtml: ((ruleStatus.eventExplanation != null) || (ruleStatus.eventExplanationHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.eventExplanationHtml) : status.blob.eventExplanationHtml,
- warning: ((ruleStatus.warning != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.warning) : status.blob.warning,
- warningHtml: ((ruleStatus.warningHtml != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.warningHtml) : status.blob.warningHtml,
- reason: ((ruleStatus.reason != null) || (ruleStatus.code != null)) ? rules.localeString(ruleStatus.reason) : status.blob.reason,
- fcmTopic: ((ruleStatus.fcmTopic != null) || (ruleStatus.code != null)) ? ruleStatus.fcmTopic : status.blob.fcmTopic,
+ blob: HealthStatusBlob.fromRuleStatus(ruleStatus,
+ rules: rules,
+ previousStatusBlob: status.blob,
historyBlob: historyEntry.blob,
),
);
@@ -952,8 +936,12 @@ class Health with Service implements NotificationsListener {
}
Future clearHistory() async {
+ List history = _history;
if (await _clearNetHistory()) {
- await _rebuildStatus();
+ if (!ListEquality().equals(history, _history)) {
+ _notify(notifyHistoryUpdated);
+ await _rebuildStatus();
+ }
return true;
}
return false;
@@ -1656,13 +1644,14 @@ class Health with Service implements NotificationsListener {
// Vaccination
bool get isVaccinated {
- return (HealthHistory.mostRecentVaccine(_history, vaccine: HealthHistoryBlob.VaccineEffective) != null);
+ HealthHistory vaccine = HealthHistory.mostRecentVaccine(Health().history);
+ return (vaccine.blob != null) && (vaccine?.blob?.isVaccineEffective ?? false) && (vaccine.dateUtc != null) && vaccine.dateUtc.isBefore(DateTime.now().toUtc());
}
// Current Server Time
Future getServerTimeUtc() async {
- //TMP: return DateTime.now().toUtc();
+//TMP: return DateTime.now().toUtc();
String url = (Config().healthUrl != null) ? "${Config().healthUrl}/covid19/time" : null;
Response response = (url != null) ? await Network().get(url, auth: Network.AppAuth) : null;
String responseBody = (response?.statusCode == 200) ? response.body : null;
@@ -1915,6 +1904,19 @@ class _RefreshOptions {
_RefreshOptions difference(_RefreshOptions other) {
return _RefreshOptions.fromSet(options?.difference(other?.options));
}
+
+ String toString() {
+ String list = '';
+ for (_RefreshOption option in _RefreshOption.values) {
+ if (options.contains(option)) {
+ if (list.isNotEmpty) {
+ list += ', ';
+ }
+ list += option.toString();
+ }
+ }
+ return '[$list]';
+ }
}
enum _RefreshOption {
diff --git a/lib/service/NativeCommunicator.dart b/lib/service/NativeCommunicator.dart
index 69e49114..7616b154 100644
--- a/lib/service/NativeCommunicator.dart
+++ b/lib/service/NativeCommunicator.dart
@@ -142,15 +142,6 @@ class NativeCommunicator with Service {
}
}
- Future microBlinkScan({List recognizers}) async {
- try {
- return await _platformChannel.invokeMethod('microBlinkScan', { 'recognizers' : recognizers });
- } on PlatformException catch (e) {
- print(e.message);
- }
- return null;
- }
-
Future> enabledOrientations(List orientationsList) async {
List result;
try {
diff --git a/lib/service/Onboarding.dart b/lib/service/Onboarding.dart
index 023f805f..911c6edd 100644
--- a/lib/service/Onboarding.dart
+++ b/lib/service/Onboarding.dart
@@ -26,8 +26,6 @@ import 'package:illinois/ui/onboarding/OnboardingHealthFinalPanel.dart';
import 'package:illinois/ui/onboarding/OnboardingHealthHowItWorksPanel.dart';
import 'package:illinois/ui/onboarding/OnboardingHealthIntroPanel.dart';
import 'package:illinois/ui/onboarding/OnboardingHealthQrCodePanel.dart';
-import 'package:illinois/ui/onboarding/OnboardingResidentInfoPanel.dart';
-import 'package:illinois/ui/onboarding/OnboardingReviewScanPanel.dart';
import 'package:illinois/ui/onboarding/OnboardingAuthBluetoothPanel.dart';
import 'package:illinois/ui/onboarding/OnboardingLoginPhoneConfirmPanel.dart';
import 'package:illinois/ui/onboarding/OnboardingGetStartedPanel.dart';
@@ -164,8 +162,6 @@ class Onboarding extends Service implements NotificationsListener{
case "login_phone": return OnboardingLoginPhonePanel(onboardingContext: context);
case "verify_phone": return OnboardingLoginPhoneVerifyPanel(onboardingContext: context);
case "confirm_phone": return OnboardingLoginPhoneConfirmPanel(onboardingContext: context);
- case "resident_info": return OnboardingResidentInfoPanel(onboardingContext: context);
- case "review_scan": return OnboardingReviewScanPanel(onboardingContext: context);
case "health_intro": return OnboardingHealthIntroPanel(onboardingContext: context);
case "health_how_it_works": return OnboardingHealthHowItWorksPanel(onboardingContext: context);
case "health_disclosure": return OnBoardingHealthDisclosurePanel(onboardingContext: context);
@@ -207,12 +203,6 @@ class Onboarding extends Service implements NotificationsListener{
else if (panel is OnboardingLoginPhoneConfirmPanel) {
return 'confirm_phone';
}
- else if (panel is OnboardingResidentInfoPanel) {
- return 'resident_info';
- }
- else if (panel is OnboardingReviewScanPanel) {
- return 'review_scan';
- }
else if (panel is OnboardingHealthIntroPanel) {
return 'health_intro';
}
diff --git a/lib/service/Storage.dart b/lib/service/Storage.dart
index 1fe60b7d..eb0a198c 100644
--- a/lib/service/Storage.dart
+++ b/lib/service/Storage.dart
@@ -487,7 +487,7 @@ class Storage with Service {
static const String _healthUserTestMonitorIntervalKey = 'health_user_test_monitor_interval';
int get healthUserTestMonitorInterval {
- return getInt(_healthUserTestMonitorIntervalKey);
+ return getInt(_healthUserTestMonitorIntervalKey, defaultValue: null);
}
set healthUserTestMonitorInterval(int value) {
diff --git a/lib/ui/debug/DebugCreateEventPanel.dart b/lib/ui/debug/DebugCreateEventPanel.dart
index 2035c0e5..13c2c7b3 100644
--- a/lib/ui/debug/DebugCreateEventPanel.dart
+++ b/lib/ui/debug/DebugCreateEventPanel.dart
@@ -295,7 +295,7 @@ class _DebugCreateEventPanelState extends State {
Row(children: [
Expanded(child:
- RoundedButton(label: "Vacc Effective",
+ RoundedButton(label: "Vaccine Effective",
textColor: Styles().colors.fillColorPrimary,
borderColor: Styles().colors.fillColorSecondary,
backgroundColor: Styles().colors.white,
@@ -306,14 +306,7 @@ class _DebugCreateEventPanelState extends State {
),
Container(width: 4,),
Expanded(child:
- RoundedButton(label: "Vacc Taken",
- textColor: Styles().colors.fillColorPrimary,
- borderColor: Styles().colors.fillColorSecondary,
- backgroundColor: Styles().colors.white,
- fontFamily: Styles().fontFamilies.bold,
- fontSize: 16, borderWidth: 2, height: 42,
- onTap:() { _onPopulate(this._sampleVacineTakenBlob); }
- ),
+ Container()
),
],),
@@ -546,25 +539,6 @@ class _DebugCreateEventPanelState extends State {
]
}''';}
- String get _sampleVacineTakenBlob {
- DateTime nowLocal = DateTime.now();
- String dateString = healthDateTimeToString(nowLocal.toUtc());
-
- String dose2String = DateFormat("MMMM d, yyyy HH:mm").format(nowLocal);
-
- DateTime dose1Local = nowLocal.subtract(Duration(days: 21));
- String dose1String = DateFormat("MMMM d, yyyy HH:mm").format(dose1Local);
-
- return '''{
- "Date": "$dateString",
- "Vaccine": "${HealthHistoryBlob.VaccineTaken}",
- "Extra": [
- {"display_name": "Vaccine", "display_value": "Sputnik V"},
- {"display_name": "2nd Dose", "display_value": "$dose2String"},
- {"display_name": "1st Dose", "display_value": "$dose1String"}
- ]
-}''';}
-
String get _sampleActionQuarantineOnBlob {
DateTime nowLocal = DateTime.now();
String dateString = healthDateTimeToString(nowLocal.toUtc());
diff --git a/lib/ui/debug/DebugHealthRulesPanel.dart b/lib/ui/debug/DebugHealthRulesPanel.dart
index 4bec5770..4beb5fd4 100644
--- a/lib/ui/debug/DebugHealthRulesPanel.dart
+++ b/lib/ui/debug/DebugHealthRulesPanel.dart
@@ -85,12 +85,12 @@ class _DebugHealthRulesPanelState extends State{
Health().loadRulesJson(countyId: _selectedCountyId).then((Map rules) {
if (mounted) {
if ((rules != null) && (Health().userTestMonitorInterval != null)) {
- dynamic constants = rules['constants'];
- if (constants == null) {
- rules['constants'] = constants = {};
+ dynamic intervals = rules['intervals'];
+ if (intervals == null) {
+ rules['intervals'] = intervals = {};
}
- if (constants is Map) {
- constants[HealthRulesSet.UserTestMonitorInterval] = Health().userTestMonitorInterval;
+ if (intervals is Map) {
+ intervals[HealthRulesSet.UserTestMonitorInterval] = Health().userTestMonitorInterval;
}
}
diff --git a/lib/ui/debug/DebugHomePanel.dart b/lib/ui/debug/DebugHomePanel.dart
index c929dcab..30b5d778 100644
--- a/lib/ui/debug/DebugHomePanel.dart
+++ b/lib/ui/debug/DebugHomePanel.dart
@@ -23,6 +23,7 @@ import 'package:flutter/services.dart';
import 'package:illinois/model/Organization.dart';
import 'package:illinois/service/Auth.dart';
import 'package:illinois/service/FirebaseMessaging.dart';
+import 'package:illinois/service/Health.dart';
import 'package:illinois/service/Localization.dart';
import 'package:illinois/service/NotificationService.dart';
import 'package:illinois/service/Organizations.dart';
@@ -57,6 +58,7 @@ class _DebugHomePanelState extends State implements Notification
String _environment;
bool _switchingEnvironment;
+ bool _removingHistory;
@override
void initState() {
@@ -218,13 +220,25 @@ class _DebugHomePanelState extends State implements Notification
onTap: _onTapCreateEvent)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5),
- child: RoundedButton(
- label: "COVID-19 Create Exposure",
+ child: Stack(children: [
+ RoundedButton(
+ label: "COVID-19 Clear History",
backgroundColor: Styles().colors.background,
fontSize: 16.0,
textColor: Styles().colors.fillColorPrimary,
borderColor: Styles().colors.fillColorPrimary,
- onTap: _onTapTraceCovid19Exposure)),
+ onTap: _onTapClearHistory),
+ Visibility(visible: _removingHistory == true, child:
+ Padding(padding: EdgeInsets.symmetric(vertical: 12), child:
+ Center(child:
+ Container(width: 24, height: 24, child:
+ CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Styles().colors.fillColorPrimary)),
+ ),
+ ),
+ ),
+ ),
+
+ ],),),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5),
child: RoundedButton(
@@ -234,6 +248,15 @@ class _DebugHomePanelState extends State implements Notification
textColor: Styles().colors.fillColorPrimary,
borderColor: Styles().colors.fillColorPrimary,
onTap: _onTapReportCovid19Symptoms)),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5),
+ child: RoundedButton(
+ label: "COVID-19 Create Exposure",
+ backgroundColor: Styles().colors.background,
+ fontSize: 16.0,
+ textColor: Styles().colors.fillColorPrimary,
+ borderColor: Styles().colors.fillColorPrimary,
+ onTap: _onTapTraceCovid19Exposure)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5),
child: RoundedButton(
@@ -381,14 +404,20 @@ class _DebugHomePanelState extends State implements Notification
Navigator.push(context, CupertinoPageRoute(builder: (context) => DebugCreateEventPanel()));
}
- void _onTapTraceCovid19Exposure() {
- Navigator.push(context, CupertinoPageRoute(builder: (context) => DebugContactTraceReportPanel()));
+ void _onTapClearHistory() {
+ if (_removingHistory != true) {
+ showDialog(context: context, builder: (context) => _buildRemoveHistoryDialog(context));
+ }
}
void _onTapReportCovid19Symptoms() {
Navigator.push(context, CupertinoPageRoute(builder: (context) => DebugSymptomsReportPanel()));
}
+ void _onTapTraceCovid19Exposure() {
+ Navigator.push(context, CupertinoPageRoute(builder: (context) => DebugContactTraceReportPanel()));
+ }
+
void _onTapCovid19Exposures() {
Navigator.push(context, CupertinoPageRoute(builder: (context) => DebugExposurePanel()));
}
@@ -607,4 +636,82 @@ class _DebugHomePanelState extends State implements Notification
});
});
}
+
+
+ //////////////////////////
+ // Delete History
+
+ Widget _buildRemoveHistoryDialog(BuildContext context) {
+ return StatefulBuilder(builder: (context, setState) {
+ return ClipRRect(borderRadius: BorderRadius.all(Radius.circular(8)), child:
+ Dialog(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),), child:
+ Column(mainAxisSize: MainAxisSize.min, children: [
+ Row(children: [
+ Expanded(child:
+ Container(decoration: BoxDecoration(color: Styles().colors.fillColorPrimary, borderRadius: BorderRadius.vertical(top: Radius.circular(8)),), child:
+ Padding(padding: EdgeInsets.all(8), child:
+ Row(children: [
+ Expanded(child:
+ Center(child:
+ Text("Clear COVID-19 event history?", style: TextStyle(fontSize: 20, color: Colors.white),),
+ ),
+ ),
+ GestureDetector(onTap: () => Navigator.pop(context), child:
+ Container(height: 30, width: 30, decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(15)), border: Border.all(color: Styles().colors.white, width: 2), ), child:
+ Center(child:
+ Text('\u00D7', style: TextStyle(fontSize: 24, color: Colors.white, ), ),
+ ),
+ ),
+ ),
+ ],),
+ ),
+ ),
+ ),
+ ],),
+ Container(height: 26,),
+ Padding(padding: const EdgeInsets.symmetric(horizontal: 18), child:
+ Text("This will permanently remove all COVID-19 event history.", textAlign: TextAlign.left, style: TextStyle(fontFamily: Styles().fontFamilies.medium, fontSize: 16, color: Colors.black),),
+ ),
+ Container(height: 26,),
+ Text("Are you sure?", textAlign: TextAlign.center, style: TextStyle(fontFamily: Styles().fontFamilies.bold, fontSize: 16, color: Colors.black), ),
+ Container(height: 16,),
+ Padding(padding: const EdgeInsets.all(8.0), child:
+ Row(mainAxisAlignment: MainAxisAlignment.center, children: [
+ Expanded(child:
+ RoundedButton(
+ onTap: () { Navigator.pop(context); },
+ backgroundColor: Colors.transparent,
+ borderColor: Styles().colors.fillColorPrimary,
+ textColor: Styles().colors.fillColorPrimary,
+ label: 'No'),
+ ),
+ Container(width: 10,),
+ Expanded(child:
+ RoundedButton(
+ onTap: () => _onClearHistory(),
+ backgroundColor: Styles().colors.fillColorSecondaryVariant,
+ borderColor: Styles().colors.fillColorSecondaryVariant,
+ textColor: Styles().colors.surface,
+ label: 'Yes',
+ height: 48,),
+ ),
+ ],),
+ ),
+ ],),
+ ),
+ );
+ },);
+ }
+
+ void _onClearHistory() {
+ Navigator.pop(context);
+
+ if (_removingHistory != true) {
+ setState(() { _removingHistory = true; });
+ Health().clearHistory().then((bool result) {
+ setState(() {_removingHistory = false;});
+ AppAlert.showDialogResult(context, (result == true) ? 'COVID-19 event history successfully cleared.' : 'Failed to clear COVID-19 event history.');
+ });
+ }
+ }
}
diff --git a/lib/ui/health/HealthHistoryPanel.dart b/lib/ui/health/HealthHistoryPanel.dart
index 2951d1db..053561a7 100644
--- a/lib/ui/health/HealthHistoryPanel.dart
+++ b/lib/ui/health/HealthHistoryPanel.dart
@@ -165,6 +165,7 @@ class _HealthHistoryPanelState extends State implements Noti
style: TextStyle(fontSize: 16, fontFamily: Styles().fontFamilies.regular, color: Styles().colors.textSurface)),
Expanded(child: Container(),),
_buildRepostButton(),
+ Visibility(visible: !kReleaseMode || Organizations().isDevEnvironment, child: _buildRemoveMyInfoButton()),
Container(height: 10,),
],
)));
@@ -205,7 +206,7 @@ class _HealthHistoryPanelState extends State implements Noti
alignment: Alignment.center,
children: [
ScalableRoundedButton(
- label: Localization().getStringEx("panel.health.covid19.history.button.repost_history.title", "Request my latest test again"),
+ label: Localization().getStringEx("panel.health.covid19.history.button.repost_history.title", "Request my vaccine and latest test again"),
hint: Localization().getStringEx("panel.health.covid19.history.button.repost_history.hint", ""),
backgroundColor: Styles().colors.surface,
fontSize: 16.0,
@@ -530,11 +531,8 @@ class _HealthHistoryEntryState extends State<_HealthHistoryEntry> with SingleTic
if (widget.historyEntry?.blob?.isVaccineEffective ?? false) {
title = Localization().getStringEx("panel.health.covid19.history.label.vaccine.effective.title", "Vaccine Effective");
}
- else if (widget.historyEntry?.blob?.isVaccineTaken ?? false) {
- title = Localization().getStringEx("panel.health.covid19.history.label.vaccine.taken.title", "Vaccine Taken");
- }
else {
- title = Localization().getStringEx("panel.health.covid19.history.label.vaccine.title", "Vaccine Taken");
+ title = Localization().getStringEx("panel.health.covid19.history.label.vaccine.title", "Vaccine");
}
String providerTitle = widget.historyEntry?.blob?.provider ?? Localization().getStringEx("app.common.label.other", "Other");
details.addAll([
diff --git a/lib/ui/health/HealthHomePanel.dart b/lib/ui/health/HealthHomePanel.dart
index 1e0a89d6..aa8717e9 100644
--- a/lib/ui/health/HealthHomePanel.dart
+++ b/lib/ui/health/HealthHomePanel.dart
@@ -22,6 +22,7 @@ import 'package:flutter_html/style.dart';
import 'package:illinois/model/Health.dart';
import 'package:illinois/service/Analytics.dart';
import 'package:illinois/service/Auth.dart';
+import 'package:illinois/service/Config.dart';
import 'package:illinois/service/FlexUI.dart';
import 'package:illinois/service/Health.dart';
import 'package:illinois/service/Organizations.dart';
@@ -265,6 +266,8 @@ class _HealthHomePanelState extends State implements Notificati
contentList.add(_buildSymptomCheckInSection());
} else if (code == 'add_test_result') {
contentList.add(_buildAddTestResultSection());
+ } else if (code == 'vaccination') {
+ contentList.add(_buildVaccinationSection());
}
}
@@ -371,9 +374,6 @@ class _HealthHomePanelState extends State implements Notificati
if (blob.isVaccineEffective) {
historyTitle = Localization().getStringEx("panel.covid19home.label.vaccine.effective.title", "Vaccine Effective");
}
- else if (blob.isVaccineTaken) {
- historyTitle = Localization().getStringEx("panel.covid19home.label.vaccine.taken.title", "Vaccine Taken");
- }
else {
historyTitle = Localization().getStringEx("panel.covid19home.label.vaccine.title", "Vaccine");
}
@@ -543,7 +543,7 @@ class _HealthHomePanelState extends State implements Notificati
borderColor: Styles().colors.fillColorSecondary,
backgroundColor: Styles().colors.surface,
textColor: Styles().colors.fillColorPrimary,
- onTap: ()=> _onTapFindLocations(),
+ onTap: ()=> _onTapFindTestLocations(),
)),
)
],),
@@ -678,6 +678,122 @@ class _HealthHomePanelState extends State implements Notificati
));
}
+ Widget _buildVaccinationSection() {
+
+ String headingDate;
+ String statusTitleText, statusTitleHtml;
+ String statusDescriptionText, statusDescriptionHtml;
+ String headingTitle = Localization().getStringEx('panel.covid19home.vaccination.heading.title', 'VACCINATION');
+
+ HealthHistory vaccine = HealthHistory.mostRecentVaccine(Health().history);
+ if (vaccine == null) {
+ // No vaccine at all - promote it.
+ statusTitleText = Localization().getStringEx('panel.covid19home.vaccination.none.title', 'Get a vaccine now');
+ statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.none.description', """
+• COVID-19 vaccines are safe
+• COVID-19 vaccines are effective
+• COVID-19 vaccines allow you to safely do more
+• COVID-19 vaccines build safer protection""");
+ }
+ else if ((vaccine.blob != null) && (vaccine.blob.isVaccineEffective) && (vaccine.dateUtc != null) && vaccine.dateUtc.isBefore(DateTime.now().toUtc())) {
+ // 5.2.4 When effective then hide the widget
+ return null;
+ }
+ else {
+ // Vaccinated, but not effective yet.
+ headingDate = AppDateTime.formatDateTime(vaccine.dateUtc?.toLocal(), format:"MMMM dd, yyyy", locale: Localization().currentLocale?.languageCode) ?? '';
+ statusTitleText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.title', 'Vaccinated');
+ statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.description', 'Your vaccination is not effective yet.');
+ }
+
+ List contentWidgets = [
+ Row(children: [
+ Text(headingTitle ?? '', style: TextStyle(letterSpacing: 0.5, fontFamily: Styles().fontFamilies.bold, fontSize: 12, color: Styles().colors.fillColorPrimary),),
+ Expanded(child: Container(),),
+ Text(headingDate ?? '', style: TextStyle(fontFamily: Styles().fontFamilies.regular, fontSize: 12, color: Styles().colors.textSurface),)
+ ],),
+ ];
+
+ if (AppString.isStringNotEmpty(statusTitleText)) {
+ contentWidgets.addAll([
+ Container(height: 12,),
+ Text(statusTitleText, style: TextStyle(fontFamily: Styles().fontFamilies.extraBold, fontSize: 20, color: Styles().colors.fillColorPrimary),),
+ ]);
+ }
+
+ if (AppString.isStringNotEmpty(statusTitleHtml)) {
+ contentWidgets.addAll([
+ Container(height: 12,),
+ Html(data: statusTitleHtml, onLinkTap: (url) => _onTapLink(url),
+ style: {
+ "body": Style(fontFamily: Styles().fontFamilies.medium, fontSize: FontSize(16), color: Styles().colors.fillColorPrimary, padding: EdgeInsets.zero, margin: EdgeInsets.zero)
+ },
+ ),
+ ]);
+ }
+
+ if (AppString.isStringNotEmpty(statusDescriptionText)) {
+ contentWidgets.addAll([
+ Container(height: 12,),
+ Text(statusDescriptionText, style: TextStyle(fontFamily: Styles().fontFamilies.medium, fontSize: 16, color: Styles().colors.fillColorPrimary),),
+ ]);
+ }
+
+ if (AppString.isStringNotEmpty(statusDescriptionHtml)) {
+ contentWidgets.addAll([
+ Container(height: 12,),
+ Html(data: statusDescriptionHtml, onLinkTap: (url) => _onTapLink(url),
+ style: {
+ "body": Style(fontFamily: Styles().fontFamilies.medium, fontSize: FontSize(16), color: Styles().colors.fillColorPrimary, padding: EdgeInsets.zero, margin: EdgeInsets.zero)
+ },
+ ),
+ ]);
+ }
+
+ List contentList = [
+ Stack(children: [
+ Visibility(visible: true,
+ child: Padding(padding: EdgeInsets.symmetric(horizontal: 16),
+ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: contentWidgets),
+ ),
+ ),
+ Visibility(visible: (_isRefreshing == true),
+ child: Container(
+ height: 80,
+ child: Align(alignment: Alignment.center,
+ child: SizedBox(height: 24, width: 24,
+ child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Styles().colors.fillColorSecondary), )
+ ),
+ ),
+ ),
+ ),
+ ],),
+ ];
+
+ if ((vaccine == null) && (Config().vaccinationAppointUrl != null)) {
+ contentList.addAll([
+ Container(margin: EdgeInsets.only(top: 14, bottom: 14), height: 1, color: Styles().colors.fillColorPrimaryTransparent015,),
+
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Semantics(explicitChildNodes: true, child: ScalableRoundedButton(
+ label: Localization().getStringEx('panel.covid19home.vaccination.button.appointment.title', 'Make an appointment'),
+ hint: Localization().getStringEx('panel.covid19home.vaccination.button.appointment.hint', ''),
+ borderColor: Styles().colors.fillColorSecondary,
+ backgroundColor: Styles().colors.surface,
+ textColor: Styles().colors.fillColorPrimary,
+ onTap: _onTapMakeVaccineAppointment,
+ )),
+ )
+ ]);
+ }
+
+ return Semantics(container: true, child:
+ Container(padding: EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration(color: Styles().colors.surface, borderRadius: BorderRadius.all(Radius.circular(4)), boxShadow: [BoxShadow(color: Styles().colors.blackTransparent018, spreadRadius: 2.0, blurRadius: 6.0, offset: Offset(2, 2))] ), child:
+ Column(crossAxisAlignment: CrossAxisAlignment.start, children: contentList),
+ ));
+ }
+
Widget _buildTileButtons() {
List contentList = [];
@@ -774,7 +890,7 @@ class _HealthHomePanelState extends State implements Notificati
hint: Localization().getStringEx("panel.covid19home.button.find_test_locations.hint", ""),
borderRadius: BorderRadius.circular(4),
height: null,
- onTap: ()=>_onTapFindLocations(),
+ onTap: ()=>_onTapFindTestLocations(),
),
);
}
@@ -929,7 +1045,7 @@ class _HealthHomePanelState extends State implements Notificati
}
}
- void _onTapFindLocations() {
+ void _onTapFindTestLocations() {
if (Connectivity().isNotOffline) {
Analytics.instance.logSelect(target: "COVID-19 Find Test Locations");
Navigator.push(context, CupertinoPageRoute(builder: (context) => HealthTestLocationsPanel()));
@@ -938,6 +1054,17 @@ class _HealthHomePanelState extends State implements Notificati
}
}
+ void _onTapMakeVaccineAppointment() {
+ if (Config().vaccinationAppointUrl != null) {
+ if (Connectivity().isNotOffline) {
+ Analytics.instance.logSelect(target: "COVID-19 Make Vaccine Appointment");
+ Navigator.push(context, CupertinoPageRoute(builder: (context) => WebPanel(url: Config().vaccinationAppointUrl)));
+ } else {
+ AppAlert.showOfflineMessage(context);
+ }
+ }
+ }
+
void _onTapGroups() {
if (Connectivity().isNotOffline) {
Analytics.instance.logSelect(target: "COVID-19 Groups");
diff --git a/lib/ui/health/HealthStatusUpdatePanel.dart b/lib/ui/health/HealthStatusUpdatePanel.dart
index 6579ab20..d1d1c97a 100644
--- a/lib/ui/health/HealthStatusUpdatePanel.dart
+++ b/lib/ui/health/HealthStatusUpdatePanel.dart
@@ -16,8 +16,12 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_html/flutter_html.dart';
+import 'package:flutter_html/style.dart';
import 'package:illinois/model/Health.dart';
+import 'package:illinois/service/Connectivity.dart';
import 'package:illinois/service/Health.dart';
+import 'package:illinois/ui/WebPanel.dart';
import 'package:illinois/utils/AppDateTime.dart';
import 'package:illinois/service/Localization.dart';
import 'package:illinois/service/Styles.dart';
@@ -25,6 +29,7 @@ import 'package:illinois/ui/health/HealthNextStepsPanel.dart';
import 'package:illinois/ui/widgets/RoundedButton.dart';
import 'package:illinois/ui/widgets/StatusInfoDialog.dart';
import 'package:illinois/utils/Utils.dart';
+import 'package:url_launcher/url_launcher.dart';
class HealthStatusUpdatePanel extends StatefulWidget {
final HealthStatus status;
@@ -154,36 +159,42 @@ class _HealthStatusUpdatePanelState extends State {
padding: EdgeInsets.symmetric(horizontal: 8),
child: Container(height: 1, color: Styles().colors.surfaceAccent ,),
),
- _buildReasonContent(),
+ _buildDetails(),
],
),
);
}
- Widget _buildReasonContent(){
+ Widget _buildDetails(){
String date = AppDateTime.formatDateTime(widget.status?.dateUtc?.toLocal(), format: "MMMM dd, yyyy", locale: Localization().currentLocale?.languageCode);
- String reasonStatusText = widget.status?.blob?.displayReason;
- HealthHistoryBlob reasonHistory = widget.status?.blob?.historyBlob;
- String reasonHistoryName;
- Widget reasonHistoryDetail;
- if (reasonHistory != null) {
- if (reasonHistory.isTest) {
- reasonHistoryName = reasonHistory.testType;
+ HealthHistoryBlob history = widget.status?.blob?.historyBlob;
+
+ String reasonText = widget.status?.blob?.displayStatusUpdateReason;
+ String reasonHtml = widget.status?.blob?.displayStatusUpdateReasonHtml;
+
+ String noticeText = widget.status?.blob?.displayStatusUpdateNotice;
+ String noticeHtml = widget.status?.blob?.displayStatusUpdateNoticeHtml;
+ Widget noticeDetailWidget;
+
+ if ((noticeText == null) && (noticeHtml == null) && (history != null)) {
+ // Build default notice content
+ if (history.isTest) {
+ noticeText = history.testType;
- reasonHistoryDetail = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
+ noticeDetailWidget = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.asset("images/icon-selected.png", excludeFromSemantics: true,),
Container(width: 7,),
Text(Localization().getStringEx("panel.health.status_update.label.reason.result", "Result:"), style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.bold)),
Container(width: 5,),
- Text(reasonHistory.testResult ?? '', style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.regular)),
+ Text(history.testResult ?? '', style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.regular)),
],);
}
- else if (reasonHistory.isSymptoms) {
- reasonHistoryName = Localization().getStringEx("panel.health.status_update.label.reason.symptoms.title", "You reported new symptoms");
+ else if (history.isSymptoms) {
+ noticeText = Localization().getStringEx("panel.health.status_update.label.reason.symptoms.title", "You reported new symptoms");
List symptomLayouts = [];
- List symptoms = reasonHistory.symptoms;
+ List symptoms = history.symptoms;
if (symptoms?.isNotEmpty ?? false) {
symptoms.forEach((HealthSymptom symptom){
String symptomName = Health().rules?.localeString(symptom?.name) ?? symptom?.name;
@@ -193,45 +204,42 @@ class _HealthStatusUpdatePanelState extends State {
});
}
- reasonHistoryDetail = Column(mainAxisAlignment: MainAxisAlignment.center, children: symptomLayouts,);
+ noticeDetailWidget = Column(mainAxisAlignment: MainAxisAlignment.center, children: symptomLayouts,);
}
- else if (reasonHistory.isContactTrace) {
- reasonHistoryName = Localization().getStringEx("panel.health.status_update.label.reason.exposed.title", "You were exposed to someone who was likely infected");
+ else if (history.isContactTrace) {
+ noticeText = Localization().getStringEx("panel.health.status_update.label.reason.exposed.title", "You were exposed to someone who was likely infected");
- reasonHistoryDetail = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
+ noticeDetailWidget = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.asset("images/icon-selected.png", excludeFromSemantics: true,),
Container(width: 7,),
Text(Localization().getStringEx("panel.health.status_update.label.reason.exposure.detail", "Duration of exposure: "), style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.bold)),
Container(width: 5,),
- Text(reasonHistory.traceDurationDisplayString ?? "", style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.regular)),
+ Text(history.traceDurationDisplayString ?? "", style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.regular)),
],);
}
- else if (reasonHistory.isVaccine) {
- if (reasonHistory.isVaccineEffective) {
- reasonHistoryName = Localization().getStringEx("panel.health.status_update.label.reason.vaccine.effective.title", "Your vaccine is already effective.");
- }
- else if (reasonHistory.isVaccineTaken) {
- reasonHistoryName = Localization().getStringEx("panel.health.status_update.label.reason.vaccine.taken.title", "Your vaccine is taken.");
+ else if (history.isVaccine) {
+ if (history.isVaccineEffective) {
+ noticeText = Localization().getStringEx("panel.health.status_update.label.reason.vaccine.effective.title", "Your COVID-19 vaccination has been verified.");
}
else {
- reasonHistoryName = Localization().getStringEx("panel.health.status_update.label.reason.vaccine.title", "Your vaccine is applied.");
+ noticeText = Localization().getStringEx("panel.health.status_update.label.reason.vaccine.title", "Your vaccine is applied.");
}
}
- else if (reasonHistory.isAction) {
- reasonHistoryName = Localization().getStringEx("panel.health.status_update.label.reason.action.title", "Health authorities require you to take an action.");
+ else if (history.isAction) {
+ noticeText = Localization().getStringEx("panel.health.status_update.label.reason.action.title", "Health authorities require you to take an action.");
- reasonHistoryDetail = Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
+ noticeDetailWidget = Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.asset("images/icon-selected.png", excludeFromSemantics: true,),
Container(width: 7,),
- Text(reasonHistory.localeActionTitle ?? Localization().getStringEx("panel.health.status_update.label.reason.action.detail", "Action Required: "), style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.bold)),
+ Text(history.localeActionTitle ?? Localization().getStringEx("panel.health.status_update.label.reason.action.detail", "Action Required: "), style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.bold)),
],),
- Text(reasonHistory.localeActionText ?? "", style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.regular)),
+ Text(history.localeActionText ?? "", style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.regular)),
],);
}
}
- if ((reasonStatusText != null) || (reasonHistoryName != null) || (reasonHistoryDetail != null)) {
+ if ((reasonText != null) || (reasonHtml != null) || (noticeText != null) || (noticeHtml != null) || (noticeDetailWidget != null)) {
List content = [
Container(height: 30,),
Text(Localization().getStringEx("panel.health.status_update.label.reason.title", "STATUS CHANGED BECAUSE:"), textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 12, fontFamily: Styles().fontFamilies.bold),),
@@ -245,25 +253,46 @@ class _HealthStatusUpdatePanelState extends State {
]);
}
- if (reasonHistoryName != null) {
- content.addAll([
- Text(reasonHistoryName,textAlign: TextAlign.center,style: TextStyle(color: Colors.white, fontSize: 18, fontFamily: Styles().fontFamilies.extraBold),),
- Container(height: 9,),
- ]);
+ if ((noticeText != null) || (noticeHtml != null)) {
+ if (noticeText != null) {
+ content.add(Text(noticeText, textAlign: TextAlign.center, style: TextStyle(fontFamily: Styles().fontFamilies.extraBold, fontSize: 18, color: Colors.white, ),), );
+ }
+ if ((noticeText != null) && (noticeHtml != null)) {
+ content.add(Container(height: 9,));
+ }
+ if (noticeHtml != null) {
+ content.add(Html(data: noticeHtml, onLinkTap: (url) => _onTapLink(url),
+ style: {
+ "body": Style(fontFamily: Styles().fontFamilies.medium, fontSize: FontSize(18), color: Styles().colors.white, padding: EdgeInsets.zero, margin: EdgeInsets.zero),
+ },
+ ),);
+ }
+ content.add(Container(height: 9,));
}
- if (reasonHistoryDetail != null) {
+ if (noticeDetailWidget != null) {
content.addAll([
- reasonHistoryDetail,
+ noticeDetailWidget,
]);
}
- if (reasonStatusText != null) {
- content.addAll([
- Container(height: 60,),
- Text(reasonStatusText, textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 14, fontFamily: Styles().fontFamilies.bold),),
- Container(height: 30,),
- ]);
+ if ((reasonText != null) || (reasonHtml != null)) {
+ content.add(Container(height: 60,));
+ if (reasonText != null) {
+ content.add(Text(reasonText, textAlign: TextAlign.center, style: TextStyle(fontFamily: Styles().fontFamilies.bold, fontSize: 14, color: Styles().colors.white,),));
+ }
+ if ((reasonText != null) && (reasonHtml != null)) {
+ content.add(Container(height: 12,));
+ }
+ if (reasonHtml != null) {
+ content.add(Html(data: reasonHtml, onLinkTap: (url) => _onTapLink(url),
+ style: {
+ "body": Style(fontFamily: Styles().fontFamilies.medium, fontSize: FontSize(14), color: Styles().colors.white, padding: EdgeInsets.zero, margin: EdgeInsets.zero),
+ },
+ ),);
+ }
+
+ content.add(Container(height: 30,));
}
return Container(
@@ -304,4 +333,18 @@ class _HealthStatusUpdatePanelState extends State {
])
);
}*/
+
+ void _onTapLink(String url) {
+ if (Connectivity().isNotOffline) {
+ if (AppString.isStringNotEmpty(url)) {
+ if (AppUrl.launchInternal(url)) {
+ Navigator.push(context, CupertinoPageRoute(builder: (context) => WebPanel(url: url)));
+ } else {
+ launch(url);
+ }
+ }
+ } else {
+ AppAlert.showOfflineMessage(context);
+ }
+ }
}
\ No newline at end of file
diff --git a/lib/ui/onboarding/OnboardingResidentInfoPanel.dart b/lib/ui/onboarding/OnboardingResidentInfoPanel.dart
deleted file mode 100644
index 7c264f76..00000000
--- a/lib/ui/onboarding/OnboardingResidentInfoPanel.dart
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2020 Board of Trustees of the University of Illinois.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
-import 'package:illinois/model/UserProfile.dart';
-import 'package:illinois/service/Analytics.dart';
-import 'package:illinois/service/Auth.dart';
-import 'package:illinois/service/Localization.dart';
-import 'package:illinois/service/NativeCommunicator.dart';
-import 'package:illinois/service/Onboarding.dart';
-import 'package:illinois/service/Styles.dart';
-import 'package:illinois/service/UserProfile.dart';
-import 'package:illinois/ui/onboarding/OnboardingHealthProgress.dart';
-import 'package:illinois/ui/onboarding/OnboardingBackButton.dart';
-import 'package:illinois/ui/widgets/RoundedButton.dart';
-import 'package:illinois/ui/widgets/ScalableScrollView.dart';
-
-class OnboardingResidentInfoPanel extends StatelessWidget with OnboardingPanel {
-
- final Map onboardingContext;
- final Function(Map) onSucceed;
- final Function onCancel;
-
- OnboardingResidentInfoPanel({this.onboardingContext, this.onSucceed, this.onCancel});
-
- @override
- bool get onboardingCanDisplay {
- return !UserProfile().isStudentOrEmployee && (onboardingContext != null) && onboardingContext['shouldDisplayResidentInfo'] == true;
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Styles().colors.background,
- body: ScalableScrollView(
- scrollableChild: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Container(color: Styles().colors.white, child: Stack(children: [
- OnboardingHealthProgress(progress: 0.50,),
- Align(alignment: Alignment.topLeft,
- child: OnboardingBackButton(image: 'images/chevron-left-blue.png', padding: EdgeInsets.only(top: 16, right: 20, bottom: 20), onTap: () => _goBack(context)),
- ),
- Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
- Padding(
- padding: EdgeInsets.only(left: 24, right: 24, top: 24, bottom: 12),
- child: Semantics( header: true, hint: Localization().getStringEx("app.common.heading.one.hint","Header 1"),
- child:Text( Localization().getStringEx('panel.health.onboarding.covid19.resident_info.label.title', 'Verify your identity with a government-issued ID',),
- textAlign: TextAlign.center,
- style: TextStyle(fontFamily: Styles().fontFamilies.extraBold, fontSize: 20, color: Styles().colors.fillColorPrimary),
- ))),
- Padding(
- padding: EdgeInsets.only(left: 24, right: 24, bottom: 19),
- child: Text(
- Localization().getStringEx('panel.health.onboarding.covid19.resident_info.label.description', 'After verifying you will receive a color-coded health status based on your county guidelines, symptoms, and any COVID-19 related tests.'),
- textAlign: TextAlign.center,
- style: TextStyle(fontFamily: Styles().fontFamilies.regular, fontSize: 16, color: Styles().colors.fillColorPrimary),
- )),
- ],)
- ],),),]),
- bottomNotScrollableWidget: Padding(
- padding: EdgeInsets.symmetric(horizontal: 24),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Padding(padding: EdgeInsets.symmetric(vertical: 17, horizontal: 16), child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
- Expanded(child:ScalableRoundedButton(
- label: Localization().getStringEx('panel.health.onboarding.covid19.resident_info.button.passport.title', 'Passport'),
- hint: Localization().getStringEx('panel.health.onboarding.covid19.resident_info.button.passport.hint', ''),
- borderColor: Styles().colors.fillColorSecondary,
- backgroundColor: Styles().colors.white,
- textColor: Styles().colors.fillColorPrimary,
- padding: EdgeInsets.symmetric(horizontal: 22),
- onTap: () => _doScan(context, UserDocumentType.passport),
- )),
- Container(width: 16,),
- Expanded(child: ScalableRoundedButton(
- label: Localization().getStringEx('panel.health.onboarding.covid19.resident_info.button.drivers_license.title', "Driver's License"),
- hint: Localization().getStringEx('panel.health.onboarding.covid19.resident_info.button.drivers_license.hint', ''),
- borderColor: Styles().colors.fillColorSecondary,
- backgroundColor: Styles().colors.white,
- textColor: Styles().colors.fillColorPrimary,
- onTap: () => _doScan(context, UserDocumentType.drivingLicense),
- ),)
- ],),),
- GestureDetector(
- onTap: () => _onTapVerifyLater(context),
- behavior: HitTestBehavior.translucent,
- child: Container(
- child: Padding(
- padding: EdgeInsets.only(bottom: 20),
- child: Text(
- Localization().getStringEx('panel.health.onboarding.covid19.resident_info.button.verify_later.title', "Verify later"),
- style: TextStyle(
- fontFamily: Styles().fontFamilies.regular,
- fontSize: 16,
- color: Styles().colors.fillColorPrimary,
- decoration: TextDecoration.underline,
- decorationColor: Styles().colors.fillColorSecondary,
- decorationThickness: 1,
- decorationStyle: TextDecorationStyle.solid),
- )),
- ),
- )
- ],
- ),)
- ));
- }
-
- void _goBack(BuildContext context) {
- Analytics.instance.logSelect(target: "Back");
- Navigator.of(context).pop();
- }
-
- void _doScan(BuildContext context, UserDocumentType documentType) {
-
- String analyticsScanType;
- List