diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt index bf1d6abe03..3706ad0f38 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2022-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,18 @@ import android.view.Gravity import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch class BehaviorListFragment : Fragment(R.layout.behavior_list_fragment) { private val viewModel: BehaviorListViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpBehaviorsRecyclerView() + (activity as? MainActivity)?.showOpenQuestionnaireMenu(true) } override fun onResume() { @@ -63,14 +66,20 @@ class BehaviorListFragment : Fragment(R.layout.behavior_list_fragment) { } private fun launchQuestionnaireFragment(behavior: BehaviorListViewModel.Behavior) { - findNavController() - .navigate( - BehaviorListFragmentDirections.actionBehaviorsFragmentToGalleryQuestionnaireFragment( - context?.getString(behavior.textId) ?: "", - behavior.questionnaireFileName, - null, - behavior.workFlow + viewLifecycleOwner.lifecycleScope.launch { + findNavController() + .navigate( + MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( + questionnaireTitleKey = context?.getString(behavior.textId) ?: "", + questionnaireJsonStringKey = + getQuestionnaireJsonStringFromAssets( + context = requireContext(), + backgroundContext = coroutineContext, + fileName = behavior.questionnaireFileName, + ), + workflow = behavior.workFlow + ) ) - ) + } } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt index b85654c921..e562c23ed2 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2022-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,11 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch /** Fragment for the component list. */ class ComponentListFragment : Fragment(R.layout.component_list_fragment) { @@ -33,6 +35,7 @@ class ComponentListFragment : Fragment(R.layout.component_list_fragment) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpComponentsRecyclerView() + (activity as? MainActivity)?.showOpenQuestionnaireMenu(true) } override fun onResume() { @@ -84,14 +87,26 @@ class ComponentListFragment : Fragment(R.layout.component_list_fragment) { } private fun launchQuestionnaireFragment(component: ComponentListViewModel.Component) { - findNavController() - .navigate( - ComponentListFragmentDirections.actionComponentsFragmentToGalleryQuestionnaireFragment( - context?.getString(component.textId) ?: "", - component.questionnaireFile, - component.questionnaireFileWithValidation, - component.workflow + viewLifecycleOwner.lifecycleScope.launch { + findNavController() + .navigate( + MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( + questionnaireTitleKey = context?.getString(component.textId) ?: "", + questionnaireJsonStringKey = + getQuestionnaireJsonStringFromAssets( + context = requireContext(), + backgroundContext = coroutineContext, + fileName = component.questionnaireFile, + ), + questionnaireWithValidationJsonStringKey = + getQuestionnaireJsonStringFromAssets( + context = requireContext(), + backgroundContext = coroutineContext, + fileName = component.questionnaireFileWithValidation, + ), + workflow = component.workflow + ) ) - ) + } } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt index 103c94fcce..590506b669 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt @@ -85,10 +85,10 @@ class DemoQuestionnaireFragment : Fragment() { childFragmentManager.setFragmentResultListener(SUBMIT_REQUEST_KEY, viewLifecycleOwner) { _, _ -> onSubmitQuestionnaireClick() } - updateArguments() if (savedInstanceState == null) { addQuestionnaireFragment() } + (activity as? MainActivity)?.showOpenQuestionnaireMenu(false) } override fun onResume() { @@ -128,27 +128,16 @@ class DemoQuestionnaireFragment : Fragment() { setHasOptionsMenu(true) } - private fun updateArguments() { - requireArguments().putString(QUESTIONNAIRE_FILE_PATH_KEY, args.questionnaireFilePathKey) - requireArguments() - .putString( - QUESTIONNAIRE_FILE_WITH_VALIDATION_PATH_KEY, - args.questionnaireFileWithValidationPathKey - ) - } - private fun addQuestionnaireFragment() { viewLifecycleOwner.lifecycleScope.launch { if (childFragmentManager.findFragmentByTag(QUESTIONNAIRE_FRAGMENT_TAG) == null) { childFragmentManager.commit { setReorderingAllowed(true) - add( - R.id.container, + val questionnaireFragment = QuestionnaireFragment.builder() - .setQuestionnaire(viewModel.getQuestionnaireJson()) - .build(), - QUESTIONNAIRE_FRAGMENT_TAG - ) + .apply { setQuestionnaire(args.questionnaireJsonStringKey!!) } + .build() + add(R.id.container, questionnaireFragment, QUESTIONNAIRE_FRAGMENT_TAG) } } } @@ -161,15 +150,15 @@ class DemoQuestionnaireFragment : Fragment() { */ private fun replaceQuestionnaireFragmentWithQuestionnaireJson() { // TODO: remove check once all files are added - if (args.questionnaireFileWithValidationPathKey.isNullOrEmpty()) { + if (args.questionnaireWithValidationJsonStringKey.isNullOrEmpty()) { return } viewLifecycleOwner.lifecycleScope.launch { val questionnaireJsonString = if (isErrorState) { - viewModel.getQuestionnaireWithValidationJson() + args.questionnaireWithValidationJsonStringKey!! } else { - viewModel.getQuestionnaireJson() + args.questionnaireJsonStringKey!! } childFragmentManager.commit { setReorderingAllowed(true) @@ -225,9 +214,6 @@ class DemoQuestionnaireFragment : Fragment() { companion object { const val QUESTIONNAIRE_FRAGMENT_TAG = "questionnaire-fragment-tag" - const val QUESTIONNAIRE_FILE_PATH_KEY = "questionnaire-file-path-key" - const val QUESTIONNAIRE_FILE_WITH_VALIDATION_PATH_KEY = - "questionnaire-file-with-validation-path-key" } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt index dba615bbd0..264225325f 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2022-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,55 +19,13 @@ package com.google.android.fhir.catalog import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.context.FhirVersionEnum -import com.google.android.fhir.catalog.DemoQuestionnaireFragment.Companion.QUESTIONNAIRE_FILE_PATH_KEY -import com.google.android.fhir.catalog.DemoQuestionnaireFragment.Companion.QUESTIONNAIRE_FILE_WITH_VALIDATION_PATH_KEY -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.hl7.fhir.r4.model.QuestionnaireResponse class DemoQuestionnaireViewModel(application: Application, private val state: SavedStateHandle) : AndroidViewModel(application) { - private val backgroundContext = viewModelScope.coroutineContext - private var questionnaireJson: String? = null - private var questionnaireWithValidationJson: String? = null - - init { - viewModelScope.launch { - getQuestionnaireJson() - // TODO remove check once all files are added - if (!state.get(QUESTIONNAIRE_FILE_WITH_VALIDATION_PATH_KEY).isNullOrEmpty()) { - getQuestionnaireWithValidationJson() - } - } - } fun getQuestionnaireResponseJson(response: QuestionnaireResponse) = FhirContext.forCached(FhirVersionEnum.R4).newJsonParser().encodeResourceToString(response) - - suspend fun getQuestionnaireJson(): String { - return withContext(backgroundContext) { - if (questionnaireJson == null) { - questionnaireJson = readFileFromAssets(state[QUESTIONNAIRE_FILE_PATH_KEY]!!) - } - questionnaireJson!! - } - } - - suspend fun getQuestionnaireWithValidationJson(): String { - return withContext(backgroundContext) { - if (questionnaireWithValidationJson == null) { - questionnaireWithValidationJson = - readFileFromAssets(state[QUESTIONNAIRE_FILE_WITH_VALIDATION_PATH_KEY]!!) - } - questionnaireWithValidationJson!! - } - } - - private suspend fun readFileFromAssets(filename: String) = - withContext(backgroundContext) { - getApplication().assets.open(filename).bufferedReader().use { it.readText() } - } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt index 0f0b37036b..080bc1fb16 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2022-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,9 +21,11 @@ import android.view.Gravity import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch /** Fragment for the layout list. */ class LayoutListFragment : Fragment(R.layout.layout_list_fragment) { @@ -38,6 +40,7 @@ class LayoutListFragment : Fragment(R.layout.layout_list_fragment) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpLayoutsRecyclerView() + (activity as? MainActivity)?.showOpenQuestionnaireMenu(true) } private fun setUpLayoutsRecyclerView() { @@ -67,14 +70,20 @@ class LayoutListFragment : Fragment(R.layout.layout_list_fragment) { } private fun launchQuestionnaireFragment(layout: LayoutListViewModel.Layout) { - findNavController() - .navigate( - LayoutListFragmentDirections.actionLayoutsFragmentToGalleryQuestionnaireFragment( - context?.getString(layout.textId) ?: "", - layout.questionnaireFileName, - null, - layout.workflow + viewLifecycleOwner.lifecycleScope.launch { + findNavController() + .navigate( + MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( + questionnaireTitleKey = context?.getString(layout.textId) ?: "", + questionnaireJsonStringKey = + getQuestionnaireJsonStringFromAssets( + context = requireContext(), + backgroundContext = coroutineContext, + fileName = layout.questionnaireFileName + ), + workflow = layout.workflow + ) ) - ) + } } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt b/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt index 5461689c44..4093da4026 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2021-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,58 @@ package com.google.android.fhir.catalog +import android.net.Uri import android.os.Bundle +import android.view.Menu +import android.view.MenuItem import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation +import androidx.navigation.findNavController import androidx.navigation.ui.NavigationUI import com.google.android.material.bottomnavigation.BottomNavigationView +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity(R.layout.activity_main) { + private var showOpenQuestionnaireMenu = true + val getContentLauncher = + registerForActivityResult(ActivityResultContracts.GetContent()) { + it?.let { launchQuestionnaireFragment(it) } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setSupportActionBar(findViewById(R.id.toolbar)) setUpBottomNavigationView() } + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.open_questionnaire_menu, menu) + return true + } + + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + menu.findItem(R.id.select_questionnaire_menu).isVisible = showOpenQuestionnaireMenu + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.select_questionnaire_menu -> { + getContentLauncher.launch("application/json") + true + } + else -> super.onOptionsItemSelected(item) + } + } + + fun showOpenQuestionnaireMenu(showMenu: Boolean) { + showOpenQuestionnaireMenu = showMenu + invalidateOptionsMenu() + } + fun showBottomNavigationView(value: Int) { findViewById(R.id.bottom_navigation_view).visibility = value } @@ -52,4 +89,22 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { val bottomNavigationView = findViewById(R.id.bottom_navigation_view) NavigationUI.setupWithNavController(bottomNavigationView, navController) } + + private fun launchQuestionnaireFragment(uri: Uri) { + lifecycleScope.launch { + findNavController(R.id.nav_host_fragment) + .navigate( + MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( + questionnaireTitleKey = "", + questionnaireJsonStringKey = + getQuestionnaireJsonStringFromFileUri( + context = applicationContext, + backgroundContext = coroutineContext, + uri = uri + ), + workflow = WorkflowType.DEFAULT, + ) + ) + } + } } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt index 0626571962..004b0dd362 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2021-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ class ModalBottomSheetFragment : BottomSheetDialogFragment() { ) NavHostFragment.findNavController(this).navigateUp() } + (activity as? MainActivity)?.showOpenQuestionnaireMenu(false) } companion object { diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireFileOperationUtil.kt b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireFileOperationUtil.kt new file mode 100644 index 0000000000..d5958593a4 --- /dev/null +++ b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireFileOperationUtil.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Google LLC + * + * 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 com.google.android.fhir.catalog + +import android.content.Context +import android.net.Uri +import java.io.BufferedReader +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +suspend fun getQuestionnaireJsonStringFromAssets( + context: Context, + backgroundContext: CoroutineContext, + fileName: String +): String { + return withContext(backgroundContext) { + context.assets.open(fileName).bufferedReader().use { it.readText() } + } +} + +suspend fun getQuestionnaireJsonStringFromFileUri( + context: Context, + backgroundContext: CoroutineContext, + uri: Uri +): String { + return withContext(backgroundContext) { + val reader = BufferedReader(context.contentResolver.openInputStream(uri)?.reader()) + reader.use { reader -> reader.readText() } + } +} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt index 025c485824..cbb06ff427 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2021-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ class QuestionnaireResponseFragment : Fragment() { setCloseOnClickListener() view.findViewById(R.id.questionnaire_response_tv).text = JSONObject(args.questionnaireResponse).toString(2) + (activity as? MainActivity)?.showOpenQuestionnaireMenu(false) } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/catalog/src/main/res/menu/open_questionnaire_menu.xml b/catalog/src/main/res/menu/open_questionnaire_menu.xml new file mode 100644 index 0000000000..6550c86ccf --- /dev/null +++ b/catalog/src/main/res/menu/open_questionnaire_menu.xml @@ -0,0 +1,10 @@ + + + diff --git a/catalog/src/main/res/navigation/nav_graph.xml b/catalog/src/main/res/navigation/nav_graph.xml index 8428f73225..e9c457908e 100644 --- a/catalog/src/main/res/navigation/nav_graph.xml +++ b/catalog/src/main/res/navigation/nav_graph.xml @@ -35,10 +35,6 @@ android:id="@+id/action_componentsFragment_to_behaviorsFragment" app:destination="@id/behaviorListFragment" /> - - - - - + - + Error Widgets Miscellaneous components + Open questionnaire