Skip to content

Commit

Permalink
Merge branch 'develop' into centralize-hasprotoextra
Browse files Browse the repository at this point in the history
  • Loading branch information
Vishwajith-Shettigar authored Jul 2, 2024
2 parents 688695b + b5354f9 commit 2bef747
Show file tree
Hide file tree
Showing 21 changed files with 745 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,15 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor(
binding.expandableHintHeader.setOnClickListener {
if (hintViewModel.isHintRevealed.get()) {
expandOrCollapseItem(position)
if (position in expandedItemIndexes)
(fragment.requireActivity() as? ViewHintListener)?.viewHint(hintIndex = position)
}
}
binding.expandHintListIcon.setOnClickListener {
if (hintViewModel.isHintRevealed.get()) {
expandOrCollapseItem(position)
if (position in expandedItemIndexes)
(fragment.requireActivity() as? ViewHintListener)?.viewHint(hintIndex = position)
}
}

Expand Down Expand Up @@ -262,11 +266,15 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor(
binding.expandableSolutionHeader.setOnClickListener {
if (solutionViewModel.isSolutionRevealed.get()) {
expandOrCollapseItem(position)
if (position in expandedItemIndexes)
(fragment.requireActivity() as? ViewSolutionInterface)?.viewSolution()
}
}
binding.expandSolutionListIcon.setOnClickListener {
if (solutionViewModel.isSolutionRevealed.get()) {
expandOrCollapseItem(position)
if (position in expandedItemIndexes)
(fragment.requireActivity() as? ViewSolutionInterface)?.viewSolution()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.oppia.android.app.hintsandsolution

/** Callback listener for when the user wishes to view a hint. */
interface ViewHintListener {
/**
* Called when the user indicates they want to view the hint corresponding to the specified
* index.
*/
fun viewHint(hintIndex: Int)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.oppia.android.app.hintsandsolution

/** Interface to check the preference regarding alert for [HintsAndSolutionDialogFragment]. */
interface ViewSolutionInterface {
/**
* Called when the user indicates they want to view the solution.
*/
fun viewSolution()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import org.oppia.android.app.hintsandsolution.HintsAndSolutionDialogFragment
import org.oppia.android.app.hintsandsolution.HintsAndSolutionListener
import org.oppia.android.app.hintsandsolution.RevealHintListener
import org.oppia.android.app.hintsandsolution.RevealSolutionInterface
import org.oppia.android.app.hintsandsolution.ViewHintListener
import org.oppia.android.app.hintsandsolution.ViewSolutionInterface
import org.oppia.android.app.model.ExplorationActivityParams
import org.oppia.android.app.model.HelpIndex
import org.oppia.android.app.model.ProfileId
Expand Down Expand Up @@ -37,7 +39,9 @@ class ExplorationActivity :
HintsAndSolutionListener,
RouteToHintsAndSolutionListener,
RevealHintListener,
ViewHintListener,
RevealSolutionInterface,
ViewSolutionInterface,
DefaultFontSizeStateListener,
HintsAndSolutionExplorationManagerListener,
ConceptCardListener,
Expand Down Expand Up @@ -188,4 +192,12 @@ class ExplorationActivity :
override fun requestVoiceOverIconSpotlight(numberOfLogins: Int) {
explorationActivityPresenter.requestVoiceOverIconSpotlight(numberOfLogins)
}

override fun viewHint(hintIndex: Int) {
explorationActivityPresenter.viewHint(hintIndex)
}

override fun viewSolution() {
explorationActivityPresenter.viewSolution()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ class ExplorationActivityPresenter @Inject constructor(
explorationFragment.revealHint(hintIndex)
}

fun viewHint(hintIndex: Int) {
val explorationFragment =
activity.supportFragmentManager.findFragmentByTag(
TAG_EXPLORATION_FRAGMENT
) as ExplorationFragment
explorationFragment.viewHint(hintIndex)
}

fun revealSolution() {
val explorationFragment =
activity.supportFragmentManager.findFragmentByTag(
Expand All @@ -409,6 +417,14 @@ class ExplorationActivityPresenter @Inject constructor(
explorationFragment.revealSolution()
}

fun viewSolution() {
val explorationFragment =
activity.supportFragmentManager.findFragmentByTag(
TAG_EXPLORATION_FRAGMENT
) as ExplorationFragment
explorationFragment.viewSolution()
}

private fun showProgressDatabaseFullDialogFragment() {
val previousFragment = activity.supportFragmentManager.findFragmentByTag(
TAG_PROGRESS_DATABASE_FULL_DIALOG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ class ExplorationFragment : InjectableFragment() {
explorationFragmentPresenter.revealHint(hintIndex)
}

fun viewHint(hintIndex: Int) {
explorationFragmentPresenter.viewHint(hintIndex)
}
fun revealSolution() {
explorationFragmentPresenter.revealSolution()
}
fun viewSolution() {
explorationFragmentPresenter.viewSolution()
}

fun dismissConceptCard() = explorationFragmentPresenter.dismissConceptCard()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,18 @@ class ExplorationFragmentPresenter @Inject constructor(
fun revealHint(hintIndex: Int) {
getStateFragment()?.revealHint(hintIndex)
}
fun viewHint(hintIndex: Int) {
getStateFragment()?.viewHint(hintIndex)
}

fun revealSolution() {
getStateFragment()?.revealSolution()
}

fun viewSolution() {
getStateFragment()?.viewSolution()
}

fun dismissConceptCard() = getStateFragment()?.dismissConceptCard()

fun getExplorationCheckpointState() = getStateFragment()?.getExplorationCheckpointState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,16 @@ class StateFragment :
stateFragmentPresenter.revealHint(hintIndex)
}

fun viewHint(hintIndex: Int) {
stateFragmentPresenter.viewHint(hintIndex)
}

fun revealSolution() = stateFragmentPresenter.revealSolution()

fun viewSolution() {
stateFragmentPresenter.viewSolution()
}

fun dismissConceptCard() = stateFragmentPresenter.dismissConceptCard()

fun getExplorationCheckpointState() = stateFragmentPresenter.getExplorationCheckpointState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,18 @@ class StateFragmentPresenter @Inject constructor(
subscribeToHintSolution(explorationProgressController.submitHintIsRevealed(hintIndex))
}

fun viewHint(hintIndex: Int) {
explorationProgressController.submitHintIsViewed(hintIndex)
}

fun revealSolution() {
subscribeToHintSolution(explorationProgressController.submitSolutionIsRevealed())
}

fun viewSolution() {
explorationProgressController.submitSolutionIsViewed()
}

private fun getAudioFragment(): Fragment? {
return fragment.childFragmentManager.findFragmentByTag(TAG_AUDIO_FRAGMENT)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ private const val SUBMIT_ANSWER_RESULT_PROVIDER_ID =
"ExplorationProgressController.submit_answer_result"
private const val SUBMIT_HINT_REVEALED_RESULT_PROVIDER_ID =
"ExplorationProgressController.submit_hint_revealed_result"
private const val SUBMIT_HINT_VIEWED_RESULT_PROVIDER_ID =
"ExplorationProgressController.submit_hint_revealed_result"
private const val SUBMIT_SOLUTION_REVEALED_RESULT_PROVIDER_ID =
"ExplorationProgressController.submit_solution_revealed_result"
private const val SUBMIT_SOLUTION_VIEWED_RESULT_PROVIDER_ID =
"ExplorationProgressController.submit_solution_revealed_result"
private const val MOVE_TO_PREVIOUS_STATE_RESULT_PROVIDER_ID =
"ExplorationProgressController.move_to_previous_state_result"
private const val MOVE_TO_NEXT_STATE_RESULT_PROVIDER_ID =
Expand Down Expand Up @@ -275,6 +279,24 @@ class ExplorationProgressController @Inject constructor(
return submitResultFlow.convertToSessionProvider(SUBMIT_HINT_REVEALED_RESULT_PROVIDER_ID)
}

/**
* Notifies the controller that the user has viewed a hint.
*
* @param hintIndex index of the hint that is being viewed
*
* @return a [DataProvider] that indicates success/failure of the operation (the actual payload of
* the result isn't relevant)
*/

fun submitHintIsViewed(hintIndex: Int): DataProvider<Any?> {
val submitResultFlow = createAsyncResultStateFlow<Any?>()
val message = ControllerMessage.LogHintIsViewed(hintIndex, activeSessionId, submitResultFlow)
sendCommandForOperation(message) {
"Failed to schedule command for viewing hint: $hintIndex."
}
return submitResultFlow.convertToSessionProvider(SUBMIT_HINT_VIEWED_RESULT_PROVIDER_ID)
}

/**
* Notifies the controller that the user has revealed the solution to the current state.
*
Expand All @@ -291,6 +313,18 @@ class ExplorationProgressController @Inject constructor(
return submitResultFlow.convertToSessionProvider(SUBMIT_SOLUTION_REVEALED_RESULT_PROVIDER_ID)
}

/**
* Notifies the controller that the user has viewed the answer.
* @return a [DataProvider] that indicates success/failure of the operation (the actual payload of
* the result isn't relevant)
*/
fun submitSolutionIsViewed(): DataProvider<Any?> {
val submitResultFlow = createAsyncResultStateFlow<Any?>()
val message = ControllerMessage.LogSolutionIsViewed(activeSessionId, submitResultFlow)
sendCommandForOperation(message) { "Failed to schedule command for viewing the solution." }
return submitResultFlow.convertToSessionProvider(SUBMIT_SOLUTION_VIEWED_RESULT_PROVIDER_ID)
}

/**
* Navigates to the previous state in the graph. If the learner is currently on the initial state,
* this method will throw an exception. Calling code is responsible for ensuring this method is
Expand Down Expand Up @@ -419,7 +453,6 @@ class ExplorationProgressController @Inject constructor(
@OptIn(ObsoleteCoroutinesApi::class)
private fun createControllerCommandActor(): SendChannel<ControllerMessage<*>> {
lateinit var controllerState: ControllerState

// Use an unlimited capacity buffer so that commands can be sent asynchronously without blocking
// the main thread or scheduling an extra coroutine.
@Suppress("JoinDeclarationAndAssignment") // Warning is incorrect in this case.
Expand Down Expand Up @@ -491,8 +524,14 @@ class ExplorationProgressController @Inject constructor(
is ControllerMessage.HintIsRevealed -> {
controllerState.submitHintIsRevealedImpl(message.callbackFlow, message.hintIndex)
}
is ControllerMessage.LogHintIsViewed ->
controllerState.logViewedHintImpl(
activeSessionId, message.hintIndex, message.callbackFlow
)
is ControllerMessage.SolutionIsRevealed ->
controllerState.submitSolutionIsRevealedImpl(message.callbackFlow)
is ControllerMessage.LogSolutionIsViewed ->
controllerState.logViewedSolutionImpl(activeSessionId, message.callbackFlow)
is ControllerMessage.MoveToPreviousState ->
controllerState.moveToPreviousStateImpl(message.callbackFlow)
is ControllerMessage.MoveToNextState ->
Expand Down Expand Up @@ -790,6 +829,43 @@ class ExplorationProgressController @Inject constructor(
}
}

private suspend fun ControllerState.logViewedHintImpl(
sessionId: String,
hintIndex: Int,
submitLogHintViewedResultFlow: MutableStateFlow<AsyncResult<Any?>>
) {
tryOperation(submitLogHintViewedResultFlow) {
check(explorationProgress.playStage != NOT_PLAYING) {
"Cannot log hint viewed if an exploration is not being played."
}
check(explorationProgress.playStage != LOADING_EXPLORATION) {
"Cannot log hint viewed if an exploration is being loaded."
}
check(explorationProgress.playStage != SUBMITTING_ANSWER) {
"Cannot log hint viewed if an answer submission is pending."
}
maybeLogViewedHint(sessionId, hintIndex)
}
}

private suspend fun ControllerState.logViewedSolutionImpl(
sessionId: String,
submitLogSolutionViewedResultFlow: MutableStateFlow<AsyncResult<Any?>>
) {
tryOperation(submitLogSolutionViewedResultFlow) {
check(explorationProgress.playStage != NOT_PLAYING) {
"Cannot log solution viewed if an exploration is not being played."
}
check(explorationProgress.playStage != LOADING_EXPLORATION) {
"Cannot log solution viewed while the exploration is being loaded."
}
check(explorationProgress.playStage != SUBMITTING_ANSWER) {
"Cannot log solution viewed if an answer submission is pending."
}
maybeLogViewedSolution(sessionId)
}
}

private fun ControllerState.maybeLogUpdatedHelpIndex(
helpIndex: HelpIndex,
activeSessionId: String
Expand All @@ -800,6 +876,25 @@ class ExplorationProgressController @Inject constructor(
}
}

private fun ControllerState.maybeLogViewedHint(
activeSessionId: String,
hintIndex: Int
) {
// Only log if the current session is active.
if (sessionId == activeSessionId) {
stateAnalyticsLogger?.logViewHint(hintIndex)
}
}

private fun ControllerState.maybeLogViewedSolution(
activeSessionId: String
) {
// Only log if the current session is active.
if (sessionId == activeSessionId) {
stateAnalyticsLogger?.logViewSolution()
}
}

private suspend fun <T> ControllerState.tryOperation(
resultFlow: MutableStateFlow<AsyncResult<T>>,
recomputeState: Boolean = true,
Expand Down Expand Up @@ -1216,12 +1311,12 @@ class ExplorationProgressController @Inject constructor(
NEXT_AVAILABLE_HINT_INDEX ->
stateAnalyticsLogger?.logHintUnlocked(newHelpIndex.nextAvailableHintIndex)
LATEST_REVEALED_HINT_INDEX ->
stateAnalyticsLogger?.logViewHint(newHelpIndex.latestRevealedHintIndex)
stateAnalyticsLogger?.logRevealHint(newHelpIndex.latestRevealedHintIndex)
SHOW_SOLUTION -> stateAnalyticsLogger?.logSolutionUnlocked()
EVERYTHING_REVEALED -> when (helpIndex.indexTypeCase) {
SHOW_SOLUTION -> stateAnalyticsLogger?.logViewSolution()
SHOW_SOLUTION -> stateAnalyticsLogger?.logRevealSolution()
NEXT_AVAILABLE_HINT_INDEX -> // No solution, so revealing the hint ends available help.
stateAnalyticsLogger?.logViewHint(helpIndex.nextAvailableHintIndex)
stateAnalyticsLogger?.logRevealHint(helpIndex.nextAvailableHintIndex)
// Nothing to do in these cases.
LATEST_REVEALED_HINT_INDEX, EVERYTHING_REVEALED, INDEXTYPE_NOT_SET, null -> {}
}
Expand Down Expand Up @@ -1349,6 +1444,31 @@ class ExplorationProgressController @Inject constructor(
override val callbackFlow: MutableStateFlow<AsyncResult<Any?>>? = null
) : ControllerMessage<Any?>()

/**
* [ControllerMessage] to log cases when the user has viewed a hint for the current session.
*
* Specific measures are taken to ensure that the handler for this message does not log the
* change if the current active session has changed (since that's generally indicative of an
* error--hints can't continue to change after the session has ended).
*/
data class LogHintIsViewed(
val hintIndex: Int,
override val sessionId: String,
override val callbackFlow: MutableStateFlow<AsyncResult<Any?>>
) : ControllerMessage<Any?>()

/**
* [ControllerMessage] to log cases when the user has viewed the solution for the current
* session.
*
* Specific measures are taken to ensure that the handler for this message does not log the
* change if the current active session has changed.
*/
data class LogSolutionIsViewed(
override val sessionId: String,
override val callbackFlow: MutableStateFlow<AsyncResult<Any?>>
) : ControllerMessage<Any?>()

/**
* [ControllerMessage] to ensure a successfully saved checkpoint is reflected in other parts of
* the app (e.g. that an exploration is considered 'in-progress' in such circumstances).
Expand Down
Loading

0 comments on commit 2bef747

Please sign in to comment.