Skip to content

Commit

Permalink
add cancel button in QuestionareFragment (google#1837)
Browse files Browse the repository at this point in the history
* add cancel button in QuestionareFragment

* sync coverage

* add confirmation dialog before canceling the questionnaire

* Run spotless

* Remove incorrect string in styles.xml

* Remove duplicate layout style attributes

* Use Android system strings for ok/cancel

* Remove duplicate review mode

* Fix build

* Fix cancel string

* Remove reading visibility of buttons from theme

* Set show cancel button default value to false

* Fix build warnings

* Remove incorrect use of custom view

* Remove unnecessary cancel button handler in the catalog app

* Remove duplicate code

* Remove duplicate code

---------

Co-authored-by: Jing Tang <[email protected]>
  • Loading branch information
Itskiprotich and jingtang10 committed Oct 2, 2023
1 parent e9e5af3 commit 18e41c0
Show file tree
Hide file tree
Showing 11 changed files with 613 additions and 241 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http:https://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.datacapture

import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import com.google.android.material.dialog.MaterialAlertDialogBuilder

internal class QuestionnaireCancelDialogFragment : DialogFragment() {

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.questionnaire_cancel_text)
.setPositiveButton(android.R.string.ok) { dialog, _ ->
setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY to RESULT_YES))
dialog?.dismiss()
}
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY to RESULT_NO))
dialog?.dismiss()
}
.create()
}

companion object {
const val TAG = "QuestionnaireCancelDialogFragment"
const val REQUEST_KEY = "request-key"
const val RESULT_KEY = "result-key"
const val RESULT_NO = "no"
const val RESULT_YES = "yes"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ class QuestionnaireFragment : Fragment() {
paginationPreviousButton.setOnClickListener { viewModel.goToPreviousPage() }
val paginationNextButton = view.findViewById<View>(R.id.pagination_next_button)
paginationNextButton.setOnClickListener { viewModel.goToNextPage() }
view.findViewById<Button>(R.id.cancel_questionnaire).setOnClickListener {
QuestionnaireCancelDialogFragment()
.show(requireActivity().supportFragmentManager, QuestionnaireCancelDialogFragment.TAG)
}

view.findViewById<Button>(R.id.submit_questionnaire).setOnClickListener {
viewModel.validateQuestionnaireAndUpdateUI().let { validationMap ->
if (validationMap.values.flatten().filterIsInstance<Invalid>().isEmpty()) {
Expand All @@ -119,6 +124,7 @@ class QuestionnaireFragment : Fragment() {
val questionnaireReviewAdapter = QuestionnaireReviewAdapter()

val submitButton = requireView().findViewById<Button>(R.id.submit_questionnaire)
val cancelButton = requireView().findViewById<Button>(R.id.cancel_questionnaire)

val reviewModeEditButton =
view.findViewById<View>(R.id.review_mode_edit_button).apply {
Expand Down Expand Up @@ -153,6 +159,8 @@ class QuestionnaireFragment : Fragment() {

// Set button visibility
submitButton.visibility = if (displayMode.showSubmitButton) View.VISIBLE else View.GONE
cancelButton.visibility = if (displayMode.showCancelButton) View.VISIBLE else View.GONE

reviewModeButton.visibility = View.GONE
reviewModeEditButton.visibility =
if (displayMode.showEditButton) {
Expand All @@ -175,9 +183,12 @@ class QuestionnaireFragment : Fragment() {
// Set button visibility
submitButton.visibility =
if (displayMode.pagination.showSubmitButton) View.VISIBLE else View.GONE
cancelButton.visibility =
if (displayMode.pagination.showCancelButton) View.VISIBLE else View.GONE
reviewModeButton.visibility =
if (displayMode.pagination.showReviewButton) View.VISIBLE else View.GONE
reviewModeEditButton.visibility = View.GONE

if (displayMode.pagination.isPaginated) {
paginationPreviousButton.visibility = View.VISIBLE
paginationPreviousButton.isEnabled = displayMode.pagination.hasPreviousPage
Expand Down Expand Up @@ -224,6 +235,7 @@ class QuestionnaireFragment : Fragment() {
paginationNextButton.visibility = View.GONE
questionnaireProgressIndicator.visibility = View.GONE
submitButton.visibility = View.GONE
cancelButton.visibility = View.GONE
reviewModeButton.visibility = View.GONE
reviewModeEditButton.visibility = View.GONE
}
Expand All @@ -234,7 +246,9 @@ class QuestionnaireFragment : Fragment() {
QuestionnaireValidationErrorMessageDialogFragment.RESULT_CALLBACK,
viewLifecycleOwner,
) { _, bundle ->
when (bundle[QuestionnaireValidationErrorMessageDialogFragment.RESULT_KEY]) {
when (
val result = bundle.getString(QuestionnaireValidationErrorMessageDialogFragment.RESULT_KEY)
) {
QuestionnaireValidationErrorMessageDialogFragment.RESULT_VALUE_FIX -> {
// Go back to the Edit mode if currently in the Review mode.
viewModel.setReviewMode(false)
Expand All @@ -244,7 +258,25 @@ class QuestionnaireFragment : Fragment() {
}
else ->
Timber.e(
"Unknown fragment result ${bundle[QuestionnaireValidationErrorMessageDialogFragment.RESULT_KEY]}",
"Unknown fragment result $result",
)
}
}
/** Listen to Button Clicks from the Cancel Dialog */
requireActivity().supportFragmentManager.setFragmentResultListener(
QuestionnaireCancelDialogFragment.REQUEST_KEY,
viewLifecycleOwner,
) { _, bundle ->
when (val result = bundle.getString(QuestionnaireCancelDialogFragment.RESULT_KEY)) {
QuestionnaireCancelDialogFragment.RESULT_NO -> {
// Allow the user to continue with the questionnaire
}
QuestionnaireCancelDialogFragment.RESULT_YES -> {
setFragmentResult(CANCEL_REQUEST_KEY, Bundle.EMPTY)
}
else ->
Timber.e(
"Unknown fragment result $result",
)
}
}
Expand Down Expand Up @@ -373,6 +405,11 @@ class QuestionnaireFragment : Fragment() {
*/
fun setShowSubmitButton(value: Boolean) = apply { args.add(EXTRA_SHOW_SUBMIT_BUTTON to value) }

/**
* A [Boolean] extra to show or hide the Cancel button in the questionnaire. Default is true.
*/
fun setShowCancelButton(value: Boolean) = apply { args.add(EXTRA_SHOW_CANCEL_BUTTON to value) }

@VisibleForTesting fun buildArgs() = bundleOf(*args.toTypedArray())

/** @return A [QuestionnaireFragment] with provided [Bundle] arguments. */
Expand Down Expand Up @@ -451,11 +488,18 @@ class QuestionnaireFragment : Fragment() {

const val SUBMIT_REQUEST_KEY = "submit-request-key"

const val CANCEL_REQUEST_KEY = "cancel-request-key"

/**
* A [Boolean] extra to show or hide the Submit button in the questionnaire. Default is true.
*/
internal const val EXTRA_SHOW_SUBMIT_BUTTON = "show-submit-button"

/**
* A [Boolean] extra to show or hide the Cancel button in the questionnaire. Default is false.
*/
internal const val EXTRA_SHOW_CANCEL_BUTTON = "show-cancel-button"

internal const val EXTRA_SHOW_OPTIONAL_TEXT = "show-optional-text"

internal const val EXTRA_SHOW_ASTERISK_TEXT = "show-asterisk-text"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
/** Flag to show/hide submit button. Default is true. */
private var shouldShowSubmitButton = state[QuestionnaireFragment.EXTRA_SHOW_SUBMIT_BUTTON] ?: true

/** Flag to show/hide cancel button. Default is false */
private var shouldShowCancelButton =
state[QuestionnaireFragment.EXTRA_SHOW_CANCEL_BUTTON] ?: false

/** Flag to control whether asterisk text is shown for required questions. */
private val showAsterisk = state[QuestionnaireFragment.EXTRA_SHOW_ASTERISK_TEXT] ?: false

Expand Down Expand Up @@ -493,6 +497,14 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
}
}

internal fun setShowSubmitButtonFlag(showSubmitButton: Boolean) {
this.shouldShowSubmitButton = showSubmitButton
}

internal fun setShowCancelButtonFlag(showCancelButton: Boolean) {
this.shouldShowCancelButton = showCancelButton
}

/** [QuestionnaireState] to be displayed in the UI. */
internal val questionnaireStateFlow: StateFlow<QuestionnaireState> =
combine(modificationCount, currentPageIndexFlow, isInReviewModeFlow) { _, _, _ ->
Expand Down Expand Up @@ -613,6 +625,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
DisplayMode.ReviewMode(
showEditButton = !isReadOnly,
showSubmitButton = !isReadOnly && shouldShowSubmitButton,
showCancelButton = !isReadOnly && shouldShowCancelButton,
),
)
}
Expand All @@ -622,18 +635,28 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
if (!questionnaire.isPaginated) {
val showReviewButton = shouldEnableReviewPage && !isInReviewModeFlow.value
val showSubmitButton = shouldShowSubmitButton && !showReviewButton
QuestionnairePagination(false, emptyList(), -1, showSubmitButton, showReviewButton)
val showCancelButton = shouldShowCancelButton && !showReviewButton
QuestionnairePagination(
false,
emptyList(),
-1,
showSubmitButton,
showCancelButton,
showReviewButton,
)
} else {
val hasNextPage =
QuestionnairePagination(pages = pages!!, currentPageIndex = currentPageIndexFlow.value!!)
.hasNextPage
val showReviewButton = shouldEnableReviewPage && !hasNextPage
val showSubmitButton = shouldShowSubmitButton && !showReviewButton && !hasNextPage
val showCancelButton = shouldShowCancelButton
QuestionnairePagination(
true,
pages!!,
currentPageIndexFlow.value!!,
showSubmitButton,
showCancelButton,
showReviewButton,
)
}
Expand Down Expand Up @@ -899,7 +922,11 @@ internal data class QuestionnaireState(
internal sealed class DisplayMode {
class EditMode(val pagination: QuestionnairePagination) : DisplayMode()

data class ReviewMode(val showEditButton: Boolean, val showSubmitButton: Boolean) : DisplayMode()
data class ReviewMode(
val showEditButton: Boolean,
val showSubmitButton: Boolean,
val showCancelButton: Boolean,
) : DisplayMode()

// Sentinel displayMode that's used in setting the initial default QuestionnaireState
object InitMode : DisplayMode()
Expand All @@ -914,6 +941,7 @@ internal data class QuestionnairePagination(
val pages: List<QuestionnairePage>,
val currentPageIndex: Int,
val showSubmitButton: Boolean = false,
val showCancelButton: Boolean = false,
val showReviewButton: Boolean = false,
)

Expand Down
52 changes: 28 additions & 24 deletions datacapture/src/main/res/layout/questionnaire_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http:https://schemas.android.com/apk/res/android"
xmlns:app="http:https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
xmlns:app="http:https://schemas.android.com/apk/res-auto"
android:layout_alignParentBottom="true"
>

<Button
android:id="@+id/review_mode_edit_button"
style="?attr/questionnaireEditButtonStyle"
app:icon="@drawable/ic_outline_edit_24"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/action_button_margin_horizontal"
android:layout_marginVertical="@dimen/action_button_margin_vertical"
app:icon="@drawable/ic_outline_edit_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
Expand All @@ -40,10 +40,10 @@
style="?attr/questionnaireLinearProgressIndicatorStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/questionnaire_edit_recycler_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/review_mode_edit_button"
app:layout_constraintBottom_toTopOf="@id/questionnaire_edit_recycler_view"
/>

<androidx.recyclerview.widget.RecyclerView
Expand All @@ -65,23 +65,40 @@
/>

<com.google.android.material.divider.MaterialDivider
style="?attr/questionnaireBottomNavContainerDividerStyle"
android:id="@+id/bottom_nav_container_divider"
style="?attr/questionnaireBottomNavContainerDividerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_container"
/>

<androidx.constraintlayout.widget.ConstraintLayout
style="?attr/questionnaireBottomNavContainerStyle"
<LinearLayout
android:id="@+id/bottom_nav_container"
style="?attr/questionnaireBottomNavContainerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
>

<Button
android:id="@+id/cancel_questionnaire"
style="?attr/questionnaireCancelButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/action_button_margin_horizontal"
android:layout_marginVertical="@dimen/action_button_margin_vertical"
android:text="@string/cancel_questionnaire"
/>

<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>

<Button
android:id="@+id/pagination_previous_button"
style="?attr/questionnaireButtonStyle"
Expand All @@ -90,10 +107,6 @@
android:layout_marginHorizontal="@dimen/action_button_margin_horizontal"
android:layout_marginVertical="@dimen/action_button_margin_vertical"
android:text="@string/button_pagination_previous"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

<Button
Expand All @@ -104,10 +117,6 @@
android:layout_marginHorizontal="@dimen/action_button_margin_horizontal"
android:layout_marginVertical="@dimen/action_button_margin_vertical"
android:text="@string/button_pagination_next"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

<Button
Expand All @@ -118,23 +127,18 @@
android:layout_marginHorizontal="@dimen/action_button_margin_horizontal"
android:layout_marginVertical="@dimen/action_button_margin_vertical"
android:text="@string/button_review"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
/>

<Button
style="?attr/questionnaireSubmitButtonStyle"
android:id="@+id/submit_questionnaire"
style="?attr/questionnaireSubmitButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/action_button_margin_horizontal"
android:layout_marginVertical="@dimen/action_button_margin_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/submit_questionnaire"
/>

</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
Loading

0 comments on commit 18e41c0

Please sign in to comment.