Skip to content

Commit

Permalink
Add read-only mode
Browse files Browse the repository at this point in the history
  • Loading branch information
jingtang10 committed Nov 29, 2022
1 parent 88b742e commit 810d514
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,58 +107,80 @@ open class QuestionnaireFragment : Fragment() {
// Listen to updates from the view model.
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.questionnaireStateFlow.collect { state ->
if (state.reviewMode) {
questionnaireItemReviewAdapter.submitList(state.items)
questionnaireReviewRecyclerView.visibility = View.VISIBLE
questionnaireEditRecyclerView.visibility = View.GONE
reviewModeEditButton.visibility = View.VISIBLE
questionnaireProgressIndicator.visibility = View.GONE
} else {
questionnaireItemEditAdapter.submitList(state.items)
questionnaireEditRecyclerView.visibility = View.VISIBLE
questionnaireReviewRecyclerView.visibility = View.GONE
reviewModeEditButton.visibility = View.GONE
questionnaireProgressIndicator.visibility = View.VISIBLE
}
when (val displayMode = state.displayMode) {
is DisplayMode.ReviewMode -> {
// Set items
questionnaireEditRecyclerView.visibility = View.GONE
questionnaireItemReviewAdapter.submitList(state.items)
questionnaireReviewRecyclerView.visibility = View.VISIBLE

// Set button visibility
submitButton.visibility = View.GONE
reviewModeButton.visibility = View.GONE
reviewModeEditButton.visibility =
if (displayMode.showEditButton) {
View.VISIBLE
} else {
View.GONE
}
paginationPreviousButton.visibility = View.GONE
paginationNextButton.visibility = View.GONE

if (state.pagination.isPaginated && !state.reviewMode) {
paginationPreviousButton.visibility = View.VISIBLE
paginationPreviousButton.isEnabled = state.pagination.hasPreviousPage
paginationNextButton.visibility = View.VISIBLE
paginationNextButton.isEnabled = state.pagination.hasNextPage
questionnaireProgressIndicator.updateProgressIndicator(
calculateProgressPercentage(
count =
(state.pagination.currentPageIndex +
1), // incremented by 1 due to initialPageIndex starts with 0.
totalCount = state.pagination.pages.size
)
)
} else {
paginationPreviousButton.visibility = View.GONE
paginationNextButton.visibility = View.GONE
// Hide progress indicator
questionnaireProgressIndicator.visibility = View.GONE
}
is DisplayMode.EditMode -> {
// Set items
questionnaireReviewRecyclerView.visibility = View.GONE
questionnaireItemEditAdapter.submitList(state.items)
questionnaireEditRecyclerView.visibility = View.VISIBLE

// Set button visibility
submitButton.visibility =
if (displayMode.pagination.showSubmitButton) 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
paginationNextButton.visibility = View.VISIBLE
paginationNextButton.isEnabled = displayMode.pagination.hasNextPage
} else {
paginationPreviousButton.visibility = View.GONE
paginationNextButton.visibility = View.GONE
}

questionnaireEditRecyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
questionnaireProgressIndicator.updateProgressIndicator(
calculateProgressPercentage(
count =
(linearLayoutManager.findLastVisibleItemPosition() +
1), // incremented by 1 due to findLastVisiblePosition() starts with 0.
totalCount = linearLayoutManager.itemCount
)
// Set progress indicator
questionnaireProgressIndicator.visibility = View.VISIBLE
if (displayMode.pagination.isPaginated) {
questionnaireProgressIndicator.updateProgressIndicator(
calculateProgressPercentage(
count =
(displayMode.pagination.currentPageIndex +
1), // incremented by 1 due to initialPageIndex starts with 0.
totalCount = displayMode.pagination.pages.size
)
}
)
} else {
questionnaireEditRecyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
questionnaireProgressIndicator.updateProgressIndicator(
calculateProgressPercentage(
count =
(linearLayoutManager.findLastVisibleItemPosition() +
1), // incremented by 1 due to findLastVisiblePosition() starts with 0.
totalCount = linearLayoutManager.itemCount
)
)
}
}
)
}
)
}
}

reviewModeButton.visibility =
if (state.pagination.showReviewButton) View.VISIBLE else View.GONE

submitButton.visibility = if (state.pagination.showSubmitButton) View.VISIBLE else View.GONE
}
}
}
Expand Down Expand Up @@ -247,7 +269,10 @@ open class QuestionnaireFragment : Fragment() {
*/
const val EXTRA_SHOW_REVIEW_PAGE_FIRST = "show-review-page-first"

/** An [Boolean] extra to control if the questionnaire is read-only. */
/**
* An [Boolean] extra to control if the questionnaire is read-only. If review page and read-only
* are both enabled, read-only will take precedence.
*/
const val EXTRA_READ_ONLY = "read-only"

const val SUBMIT_REQUEST_KEY = "submit-request-key"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,20 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
}
}

/** Flag to determine if the questionnaire should be read-only. */
private val isReadOnly = state[QuestionnaireFragment.EXTRA_READ_ONLY] ?: false

/** Flag to support fragment for review-feature */
private val shouldEnableReviewPage =
state[QuestionnaireFragment.EXTRA_ENABLE_REVIEW_PAGE] ?: false

/** Flag to open fragment first in data-collection or review-mode */
private val shouldShowReviewPageFirst =
shouldEnableReviewPage && state[QuestionnaireFragment.EXTRA_SHOW_REVIEW_PAGE_FIRST] ?: false

/** Flag to show/hide submit button. */
private var shouldShowSubmitButton = false

/** The pages of the questionnaire, or null if the questionnaire is not paginated. */
@VisibleForTesting var pages: List<QuestionnairePage>? = null

Expand All @@ -216,24 +230,11 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
*/
@VisibleForTesting val currentPageIndexFlow: MutableStateFlow<Int?> = MutableStateFlow(null)

/** Flag to support fragment for review-feature */
private val enableReviewPage = state[QuestionnaireFragment.EXTRA_ENABLE_REVIEW_PAGE] ?: false

/** Flag to open fragment first in data-collection or review-mode */
private val showReviewPageFirst =
enableReviewPage && state[QuestionnaireFragment.EXTRA_SHOW_REVIEW_PAGE_FIRST] ?: false

/** Flag to determine if the questionnaire should be read-only. */
private val readOnly = state[QuestionnaireFragment.EXTRA_READ_ONLY] ?: false

/** Tracks modifications in order to update the UI. */
private val modificationCount = MutableStateFlow(0)

/** Toggles review mode. */
private val reviewFlow = MutableStateFlow(showReviewPageFirst)

/** Flag to show/hide submit button. */
private var showSubmitButtonFlag = false
private val isInReviewModeFlow = MutableStateFlow(shouldShowReviewPageFirst)

/**
* Contains [QuestionnaireResponse.QuestionnaireResponseItemComponent]s that have been modified by
Expand Down Expand Up @@ -352,23 +353,23 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
}

internal fun setReviewMode(reviewModeFlag: Boolean) {
reviewFlow.value = reviewModeFlag
isInReviewModeFlow.value = reviewModeFlag
}

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

/** [QuestionnaireState] to be displayed in the UI. */
internal val questionnaireStateFlow: StateFlow<QuestionnaireState> =
combine(modificationCount, currentPageIndexFlow, reviewFlow) { _, _, reviewMode ->
getQuestionnaireState(reviewMode = reviewMode)
combine(modificationCount, currentPageIndexFlow, isInReviewModeFlow) { _, _, reviewMode ->
getQuestionnaireState()
}
.stateIn(
viewModelScope,
SharingStarted.Lazily,
initialValue =
getQuestionnaireState(reviewMode = enableReviewPage)
getQuestionnaireState()
.also { detectExpressionCyclicDependency(questionnaire.item) }
.also {
questionnaire.item.flattened().forEach {
Expand Down Expand Up @@ -494,56 +495,59 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
*
* The traverse is carried out in the two lists in tandem.
*/
private fun getQuestionnaireState(
reviewMode: Boolean,
): QuestionnaireState {
private fun getQuestionnaireState(): QuestionnaireState {
val questionnaireItemList = questionnaire.item
val questionnaireResponseItemList = questionnaireResponse.item

// Single-page questionnaire
if (!questionnaire.isPaginated || reviewMode) {
val showReviewButton = enableReviewPage && !reviewFlow.value
val showSubmitButton = showSubmitButtonFlag && !showReviewButton
return QuestionnaireState(
items = getQuestionnaireItemViewItems(questionnaireItemList, questionnaireResponseItemList),
pagination =
QuestionnairePagination(false, emptyList(), -1, showSubmitButton, showReviewButton),
reviewMode = reviewMode
)
} else {
// Paginated questionnaire
pages = getQuestionnairePages()
if (currentPageIndexFlow.value == null) {
currentPageIndexFlow.value = pages!!.first { it.enabled && !it.hidden }.index
// Only display items on the current page while editing a paginated questionnaire, otherwise,
// display all items.
val questionnaireItemViewItems =
if (!isReadOnly && !isInReviewModeFlow.value && questionnaire.isPaginated) {
pages = getQuestionnairePages()
if (currentPageIndexFlow.value == null) {
currentPageIndexFlow.value = pages!!.first { it.enabled && !it.hidden }.index
}
getQuestionnaireItemViewItems(
questionnaireItemList[currentPageIndexFlow.value!!],
questionnaireResponseItemList[currentPageIndexFlow.value!!]
)
} else {
getQuestionnaireItemViewItems(questionnaireItemList, questionnaireResponseItemList)
}

val showReviewButton =
enableReviewPage &&
!reviewFlow.value &&
!QuestionnairePagination(pages = pages!!, currentPageIndex = currentPageIndexFlow.value!!)
.hasNextPage
val showSubmitButton =
showSubmitButtonFlag &&
!showReviewButton &&
!QuestionnairePagination(pages = pages!!, currentPageIndex = currentPageIndexFlow.value!!)
.hasNextPage
// Reviewing the questionnaire or the questionnaire is read-only
if (isReadOnly || isInReviewModeFlow.value) {
return QuestionnaireState(
items =
getQuestionnaireItemViewItems(
questionnaireItemList[currentPageIndexFlow.value!!],
questionnaireResponseItemList[currentPageIndexFlow.value!!]
),
pagination =
QuestionnairePagination(
true,
pages!!,
currentPageIndexFlow.value!!,
showSubmitButton,
showReviewButton
),
reviewMode = false
items = questionnaireItemViewItems,
displayMode = DisplayMode.ReviewMode(showEditButton = !isReadOnly)
)
}

// Editing the questionnaire
val questionnairePagination =
if (!questionnaire.isPaginated) {
val showReviewButton = shouldEnableReviewPage && !isInReviewModeFlow.value
val showSubmitButton = shouldShowSubmitButton && !showReviewButton
QuestionnairePagination(false, emptyList(), -1, showSubmitButton, showReviewButton)
} else {
val hasNextPage =
QuestionnairePagination(pages = pages!!, currentPageIndex = currentPageIndexFlow.value!!)
.hasNextPage
val showReviewButton = shouldEnableReviewPage && !hasNextPage
val showSubmitButton = shouldShowSubmitButton && !showReviewButton && !hasNextPage
QuestionnairePagination(
true,
pages!!,
currentPageIndexFlow.value!!,
showSubmitButton,
showReviewButton
)
}

return QuestionnaireState(
items = questionnaireItemViewItems,
displayMode = DisplayMode.EditMode(questionnairePagination)
)
}

/**
Expand Down Expand Up @@ -805,12 +809,15 @@ typealias ItemToParentMap =
internal data class QuestionnaireState(
/** The items that should be currently-rendered into the Fragment. */
val items: List<QuestionnaireItemViewItem>,
/** The pagination state of the questionnaire. */
val pagination: QuestionnairePagination,
/** Tracks reviewMode in order to update the UI. */
val reviewMode: Boolean,
val displayMode: DisplayMode,
)

internal sealed class DisplayMode {
class EditMode(val pagination: QuestionnairePagination) : DisplayMode()
class ReviewMode(val showEditButton: Boolean) : DisplayMode()
}

/**
* Pagination information of the questionnaire. This is used for the UI to render pagination
* controls. Includes information for each page and the current page index.
Expand Down
Loading

0 comments on commit 810d514

Please sign in to comment.