diff --git a/android/buildSrc/src/main/kotlin/fhir-properties.gradle.kts b/android/buildSrc/src/main/kotlin/fhir-properties.gradle.kts index f31e8c0fb5..86a1c8553c 100644 --- a/android/buildSrc/src/main/kotlin/fhir-properties.gradle.kts +++ b/android/buildSrc/src/main/kotlin/fhir-properties.gradle.kts @@ -1,5 +1,5 @@ val fhirAuthArray = arrayOf( - "FHIR_BASE_URL", "OAUTH_BASE_URL", "OAUTH_CIENT_ID", "OAUTH_CLIENT_SECRET", "OAUTH_SCOPE", "APP_ID" + "FHIR_BASE_URL", "OAUTH_BASE_URL", "OAUTH_CIENT_ID", "OAUTH_CLIENT_SECRET", "OAUTH_SCOPE", "APP_ID", "FHIR_HELPER_SERVICE" ) //KEYSTORE CREDENTIALS val keystoreAuthArray = arrayOf( diff --git a/android/dataclerk/build.gradle.kts b/android/dataclerk/build.gradle.kts index f2122c7722..81045fb666 100644 --- a/android/dataclerk/build.gradle.kts +++ b/android/dataclerk/build.gradle.kts @@ -39,6 +39,11 @@ android { """"${project.extra["OAUTH_CLIENT_SECRET"]}"""", ) buildConfigField("String", "OAUTH_SCOPE", """"${project.extra["OAUTH_SCOPE"]}"""") + buildConfigField( + "String", + "FHIR_HELPER_SERVICE", + """"${project.extra["FHIR_HELPER_SERVICE"]}"""", + ) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } diff --git a/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkConfigService.kt b/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkConfigService.kt index 45d83cee53..9096ab9d81 100644 --- a/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkConfigService.kt +++ b/android/dataclerk/src/main/java/org/dtree/fhircore/dataclerk/DataClerkConfigService.kt @@ -38,6 +38,7 @@ class DataClerkConfigService @Inject constructor(@ApplicationContext val context clientId = BuildConfig.OAUTH_CIENT_ID, clientSecret = BuildConfig.OAUTH_CLIENT_SECRET, accountType = BuildConfig.APPLICATION_ID, + fhirHelperServiceBaseUrl = BuildConfig.FHIR_HELPER_SERVICE, ) override fun defineResourceTags() = diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/AuthConfiguration.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/AuthConfiguration.kt index 975b2b5636..d40895b76e 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/AuthConfiguration.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/AuthConfiguration.kt @@ -22,6 +22,7 @@ data class AuthConfiguration( var fhirServerBaseUrl: String, var clientId: String, var clientSecret: String, + var fhirHelperServiceBaseUrl: String, var accountType: String, var scope: String = "openid", ) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/fhir/helper/FhirHelperRepository.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/fhir/helper/FhirHelperRepository.kt new file mode 100644 index 0000000000..f746c3ed84 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/fhir/helper/FhirHelperRepository.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.data.remote.fhir.helper + +import javax.inject.Inject +import org.smartregister.fhircore.engine.data.remote.model.helper.FacilityResultData +import timber.log.Timber + +class FhirHelperRepository @Inject constructor(val fhirHelperService: FhirHelperService) { + suspend fun getFacilityStats(id: String): FacilityResultData { + try { + val response = fhirHelperService.fetchDailyFacilityStats(id) + if (!response.isSuccessful) { + throw Exception("Failed to fetch stats") + } + return response.body() ?: throw Exception("Failed to fetch stats") + } catch (e: Exception) { + Timber.e(e) + throw Exception("Failed to fetch stats") + } + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/fhir/helper/FhirHelperService.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/fhir/helper/FhirHelperService.kt new file mode 100644 index 0000000000..74bcd2f5a6 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/fhir/helper/FhirHelperService.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.data.remote.fhir.helper + +import org.smartregister.fhircore.engine.data.remote.model.helper.FacilityResultData +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Path + +interface FhirHelperService { + @GET("/stats/facility/{id}") + suspend fun fetchDailyFacilityStats(@Path("id") id: String): Response +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/model/helper/FacilityResultData.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/model/helper/FacilityResultData.kt new file mode 100644 index 0000000000..79c2ea4da7 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/remote/model/helper/FacilityResultData.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.data.remote.model.helper + +import java.time.LocalDate +import java.time.LocalDateTime + +data class FacilityResultData( + val groups: List, + val date: LocalDate, + val generatedDate: LocalDateTime, +) + +data class GroupedSummaryItem( + val groupKey: String, + val groupTitle: String, + val summaries: List, + val order: Int, + val startCollapsed: Boolean = false, +) + +data class SummaryItem(val name: String, val value: Int) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/NetworkModule.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/NetworkModule.kt index 20e5208fb7..7b548f68e2 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/NetworkModule.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/NetworkModule.kt @@ -27,6 +27,8 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import java.time.LocalDate +import java.time.LocalDateTime import java.util.TimeZone import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -39,9 +41,12 @@ import okhttp3.logging.HttpLoggingInterceptor import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.data.remote.auth.KeycloakService import org.smartregister.fhircore.engine.data.remote.auth.OAuthService +import org.smartregister.fhircore.engine.data.remote.fhir.helper.FhirHelperService import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirConverterFactory import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceService import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator +import org.smartregister.fhircore.engine.util.LocalDateAdapter +import org.smartregister.fhircore.engine.util.LocalDateTimeTypeAdapter import org.smartregister.fhircore.engine.util.TimeZoneTypeAdapter import org.smartregister.fhircore.engine.util.extension.getCustomJsonParser import retrofit2.Retrofit @@ -100,6 +105,8 @@ class NetworkModule { GsonBuilder() .setLenient() .registerTypeAdapter(TimeZone::class.java, TimeZoneTypeAdapter().nullSafe()) + .registerTypeAdapter(LocalDate::class.java, LocalDateAdapter()) + .registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeTypeAdapter()) .create() @Provides fun provideParser(): IParser = FhirContext.forR4Cached().getCustomJsonParser() @@ -168,6 +175,22 @@ class NetworkModule { fun provideFhirResourceService(@RegularRetrofit retrofit: Retrofit): FhirResourceService = retrofit.create(FhirResourceService::class.java) + @Provides + fun provideFhirHelperService( + @WithAuthorizationOkHttpClientQualifier okHttpClient: OkHttpClient, + configService: ConfigService, + gson: Gson, + parser: IParser, + ): FhirHelperService { + val retrofit = + Retrofit.Builder() + .baseUrl(configService.provideAuthConfiguration().fhirHelperServiceBaseUrl) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build() + return retrofit.create(FhirHelperService::class.java) + } + @Provides fun providesGenericFhirClient( @WithAuthorizationOkHttpClientQualifier okHttpClient: OkHttpClient, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/components/DataFetchingContainer.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/components/DataFetchingContainer.kt new file mode 100644 index 0000000000..23252975aa --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/components/DataFetchingContainer.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.smartregister.fhircore.engine.domain.util.DataLoadState + +@Composable +fun DataFetchingContainer( + state: DataLoadState, + modifier: Modifier = Modifier, + retry: () -> Unit, + success: @Composable (state: DataLoadState.Success) -> Unit, +) { + Column( + modifier, + ) { + when (state) { + is DataLoadState.Error -> + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize(), + ) { + ErrorMessage( + message = "Something went wrong while fetching data..", + onClickRetry = retry, + ) + } + is DataLoadState.Success -> success(state) + else -> + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize(), + ) { + CircularProgressBar( + text = "Fetching data", + ) + } + } + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/reports/FacilityReportScreen.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/reports/FacilityReportScreen.kt new file mode 100644 index 0000000000..5d224b3ef4 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/reports/FacilityReportScreen.kt @@ -0,0 +1,212 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.ui.reports + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Card +import androidx.compose.material.Chip +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.twotone.KeyboardArrowDown +import androidx.compose.material.icons.twotone.KeyboardArrowUp +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import java.time.LocalDate +import java.time.LocalDateTime +import org.smartregister.fhircore.engine.R +import org.smartregister.fhircore.engine.data.remote.model.helper.FacilityResultData +import org.smartregister.fhircore.engine.data.remote.model.helper.GroupedSummaryItem +import org.smartregister.fhircore.engine.data.remote.model.helper.SummaryItem +import org.smartregister.fhircore.engine.domain.util.DataLoadState +import org.smartregister.fhircore.engine.ui.components.DataFetchingContainer +import org.smartregister.fhircore.engine.ui.theme.SubtitleTextColor +import org.smartregister.fhircore.engine.util.extension.format + +@Composable +fun FacilityReportScreen( + navController: NavController, + viewModel: FacilityReportViewModel = hiltViewModel(), +) { + val statsState by viewModel.statsFlow.collectAsState() + + FacilityReportScreenContainer( + statsState = statsState, + navigateUp = { navController.navigateUp() }, + refresh = viewModel::loadStats, + ) +} + +@Composable +fun FacilityReportScreenContainer( + statsState: DataLoadState, + navigateUp: () -> Unit, + refresh: () -> Unit, +) { + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = stringResource(R.string.reports)) }, + navigationIcon = { + IconButton(onClick = navigateUp) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } + }, + contentColor = Color.White, + backgroundColor = MaterialTheme.colors.primary, + actions = { + IconButton(onClick = refresh) { Icon(Icons.Filled.Refresh, contentDescription = "") } + }, + ) + }, + backgroundColor = Color(0xfffbfbfb), + ) { paddingValues -> + DataFetchingContainer( + state = statsState, + retry = refresh, + modifier = Modifier.padding(paddingValues).padding(horizontal = 16.dp).fillMaxSize(), + ) { + ReportContainer(it.data) + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ReportContainer(facilityData: FacilityResultData) { + LazyColumn { + item { Box(Modifier.height(14.dp)) } + item { + Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { + Chip(onClick = {}) { Text(text = facilityData.generatedDate.format()) } + } + } + for (group in facilityData.groups.sortedBy { it.order }) { + item { + var collapsed by rememberSaveable { mutableStateOf(!group.startCollapsed) } + Card( + elevation = 1.dp, + backgroundColor = Color.White, + modifier = Modifier.padding(vertical = 6.dp), + ) { + Column( + Modifier.padding(12.dp).fillMaxWidth(), + ) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(4.dp).fillMaxWidth().clickable { collapsed = !collapsed }, + ) { + Text( + text = group.groupTitle, + style = MaterialTheme.typography.h6, + modifier = Modifier, + ) + IconButton(onClick = { collapsed = !collapsed }) { + Icon( + if (!collapsed) { + Icons.TwoTone.KeyboardArrowDown + } else Icons.TwoTone.KeyboardArrowUp, + contentDescription = "", + ) + } + } + + if (collapsed) { + Box(modifier = Modifier.height(6.dp)) + repeat(group.summaries.count()) { idx -> + val summary = group.summaries[idx] + Column( + Modifier.fillMaxWidth().padding(12.dp), + ) { + Text( + text = summary.name, + color = SubtitleTextColor, + style = MaterialTheme.typography.subtitle2, + modifier = Modifier.wrapContentWidth(), + ) + Text(text = summary.value.toString(), style = MaterialTheme.typography.subtitle1) + } + } + } + } + } + } + } + } +} + +@Preview +@Composable +fun ReportContainerPreview() { + val group = + GroupedSummaryItem( + "totals", + "Totals", + listOf( + SummaryItem(name = "Newly diagnosed clients (all)", value = 2), + SummaryItem(name = "Already on Art (all)", value = 2), + SummaryItem(name = "Exposed infant (all)", value = 2), + ), + order = 1, + ) + val data = + FacilityResultData( + groups = + listOf( + group.copy( + startCollapsed = true, + ), + group, + ), + date = LocalDate.now(), + generatedDate = LocalDateTime.now(), + ) + + FacilityReportScreenContainer( + statsState = DataLoadState.Success(data), + refresh = {}, + navigateUp = {}, + ) +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/reports/FacilityReportViewModel.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/reports/FacilityReportViewModel.kt new file mode 100644 index 0000000000..44852daff4 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/reports/FacilityReportViewModel.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.ui.reports + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import org.hl7.fhir.r4.model.ResourceType +import org.smartregister.fhircore.engine.data.remote.fhir.helper.FhirHelperRepository +import org.smartregister.fhircore.engine.data.remote.model.helper.FacilityResultData +import org.smartregister.fhircore.engine.domain.util.DataLoadState +import org.smartregister.fhircore.engine.util.SharedPreferencesHelper + +@HiltViewModel +class FacilityReportViewModel +@Inject +constructor(val sharedPreferences: SharedPreferencesHelper, val repository: FhirHelperRepository) : + ViewModel() { + val statsFlow = MutableStateFlow>(DataLoadState.Loading) + + init { + loadStats() + } + + fun loadStats() { + viewModelScope.launch(Dispatchers.IO) { + try { + statsFlow.value = DataLoadState.Loading + val location = + sharedPreferences + .read>( + key = ResourceType.Location.name, + decodeWithGson = true, + ) + ?.firstOrNull() ?: throw Exception("Failed to get location Id") + val data = repository.getFacilityStats(location) + statsFlow.value = DataLoadState.Success(data) + } catch (e: Exception) { + statsFlow.value = DataLoadState.Error(e) + } + } + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/LocalDateAdapter.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/LocalDateAdapter.kt new file mode 100644 index 0000000000..4bb4e5f7f9 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/LocalDateAdapter.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * 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. + */ + +package org.smartregister.fhircore.engine.util + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import java.lang.reflect.Type +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +class LocalDateAdapter : JsonSerializer, JsonDeserializer { + override fun serialize( + src: LocalDate?, + typeOfSrc: Type?, + context: JsonSerializationContext?, + ): JsonElement { + return JsonPrimitive(src?.format(formatter)) + } + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext?, + ): LocalDate? { + return if (json != null) { + LocalDate.parse(json.asJsonPrimitive.asString, formatter) + } else { + null + } + } + + companion object { + private val formatter = DateTimeFormatter.ISO_LOCAL_DATE + } +} + +class LocalDateTimeTypeAdapter : JsonSerializer, JsonDeserializer { + override fun serialize( + localDateTime: LocalDateTime?, + srcType: Type?, + context: JsonSerializationContext?, + ): JsonElement { + return JsonPrimitive(formatter.format(localDateTime)) + } + + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext?, + ): LocalDateTime { + return LocalDateTime.parse(json.asString, formatter) + } + + companion object { + private val formatter = DateTimeFormatter.ISO_DATE_TIME + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/DateTimeExtension.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/DateTimeExtension.kt index cdbd7e35ba..1c592a4e2a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/DateTimeExtension.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/DateTimeExtension.kt @@ -17,8 +17,12 @@ package org.smartregister.fhircore.engine.util.extension import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.LocalDateTime import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import java.time.format.DateTimeFormatterBuilder +import java.time.temporal.ChronoField import java.util.Calendar import java.util.Date import java.util.Locale @@ -94,6 +98,23 @@ fun Date.plusDays(days: Int): Date { fun DateType.format(): String = SDF_YYYY_MM_DD.format(value) +fun LocalDate.format(): String = format(DateTimeFormatter.ISO_LOCAL_DATE) + +fun LocalDateTime.format(): String = + format( + DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .toFormatter(), + ) + fun DateTimeType.format(): String = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(value).let { StringBuilder(it).insert(it.length - 2, ":").toString() diff --git a/android/quest/build.gradle.kts b/android/quest/build.gradle.kts index 04e6dbe0b7..30477b4538 100644 --- a/android/quest/build.gradle.kts +++ b/android/quest/build.gradle.kts @@ -36,6 +36,11 @@ android { buildConfigField("String", "OAUTH_BASE_URL", """"${project.extra["OAUTH_BASE_URL"]}"""") buildConfigField("String", "OAUTH_CIENT_ID", """"${project.extra["OAUTH_CIENT_ID"]}"""") buildConfigField("String", "APP_ID", """"${project.extra["APP_ID"]}"""") + buildConfigField( + "String", + "FHIR_HELPER_SERVICE", + """"${project.extra["FHIR_HELPER_SERVICE"]}"""", + ) buildConfigField( "String", "OAUTH_CLIENT_SECRET", @@ -135,21 +140,21 @@ android { applicationIdSuffix = ".mwcore" versionNameSuffix = "-mwcore" versionCode = 37 - versionName = "0.2.0.1" + versionName = "0.2.0.2-beta1" } create("mwcoreDev") { dimension = "apps" applicationIdSuffix = ".mwcoreDev" versionNameSuffix = "-mwcoreDev" versionCode = 37 - versionName = "0.2.0.1" + versionName = "0.2.0.2-beta1" } create("mwcoreStaging") { dimension = "apps" applicationIdSuffix = ".mwcoreStaging" versionNameSuffix = "-mwcoreStaging" versionCode = 37 - versionName = "0.2.0.1" + versionName = "0.2.0.2-beta1" } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestConfigService.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestConfigService.kt index 46ffa5650a..11b6c0cc8b 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestConfigService.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestConfigService.kt @@ -38,6 +38,7 @@ class QuestConfigService @Inject constructor(@ApplicationContext val context: Co clientId = BuildConfig.OAUTH_CIENT_ID, clientSecret = BuildConfig.OAUTH_CLIENT_SECRET, accountType = context.getString(R.string.authenticator_account_type), + fhirHelperServiceBaseUrl = BuildConfig.FHIR_HELPER_SERVICE, ) override fun defineResourceTags() = diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt index 52280278dc..3718ed5e6e 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt @@ -92,7 +92,7 @@ constructor( enableDeviceToDeviceSync = appFeatureManager.isFeatureActive(AppFeature.DeviceToDeviceSync), // Disable in-app reporting -- Measure reports not well supported // enableReports = appFeatureManager.isFeatureActive(AppFeature.InAppReporting), - enableReports = false, + enableReports = true, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/report/measure/MeasureReportMainScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/report/measure/MeasureReportMainScreen.kt index a051f7be35..66fd476720 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/report/measure/MeasureReportMainScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/report/measure/MeasureReportMainScreen.kt @@ -22,10 +22,10 @@ import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.compose.navigation import androidx.navigation.navArgument +import org.smartregister.fhircore.engine.ui.reports.FacilityReportScreen import org.smartregister.fhircore.quest.navigation.MainNavigationScreen import org.smartregister.fhircore.quest.navigation.MeasureReportNavigationScreen import org.smartregister.fhircore.quest.navigation.NavigationArg -import org.smartregister.fhircore.quest.ui.report.measure.screens.MeasureReportListScreen import org.smartregister.fhircore.quest.ui.report.measure.screens.MeasureReportPatientsScreen import org.smartregister.fhircore.quest.ui.report.measure.screens.MeasureReportResultScreen import org.smartregister.fhircore.quest.ui.report.measure.screens.ReportTypeSelectorScreen @@ -39,15 +39,20 @@ fun NavGraphBuilder.measureReportNavigationGraph( route = MainNavigationScreen.Reports.route, ) { // Display list of supported measures for reporting + // composable(MeasureReportNavigationScreen.MeasureReportList.route) { + // MeasureReportListScreen( + // navController = navController, + // dataList = measureReportViewModel.reportMeasuresList(), + // onReportMeasureClicked = { measureReportRowData -> + // measureReportViewModel.onEvent( + // MeasureReportEvent.OnSelectMeasure(measureReportRowData, navController), + // ) + // }, + // ) + // } composable(MeasureReportNavigationScreen.MeasureReportList.route) { - MeasureReportListScreen( + FacilityReportScreen( navController = navController, - dataList = measureReportViewModel.reportMeasuresList(), - onReportMeasureClicked = { measureReportRowData -> - measureReportViewModel.onEvent( - MeasureReportEvent.OnSelectMeasure(measureReportRowData, navController), - ) - }, ) } // Choose report type; for either individual or population