Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,16 @@ class HintsAndSolutionDialogFragment :

override fun onStart() {
super.onStart()
val dialog = this.dialog ?: return
dialog.window?.setWindowAnimations(R.style.FullScreenHintDialogStyle)
hintsAndSolutionDialogFragmentPresenter.applyEdgeToEdgeInsets(dialog)
dialog?.let { dialog ->
dialog.window?.setWindowAnimations(R.style.FullScreenHintDialogStyle)
hintsAndSolutionDialogFragmentPresenter.applyEdgeToEdgeInsets(dialog)
}
hintsAndSolutionDialogFragmentPresenter.handleOnStart()
}

override fun onStop() {
super.onStop()
hintsAndSolutionDialogFragmentPresenter.handleOnStop()
}

override fun onSaveInstanceState(outState: Bundle) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor(
)
}

/** Notifies the parent activity that the dialog has started and hint timers should pause. */
fun handleOnStart() {
(activity as? HintsAndSolutionListener)?.pauseHints()
}

/**
* Notifies the parent activity that the dialog has stopped and hint timers should resume,
* provided the stop isn't due to a configuration change.
*/
fun handleOnStop() {
if (!activity.isChangingConfigurations) {
(activity as? HintsAndSolutionListener)?.resumeHints()
}
}

private enum class ViewType {
VIEW_TYPE_HINT_ITEM,
VIEW_TYPE_SOLUTION_ITEM,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package org.oppia.android.app.hintsandsolution

/** Allows parent activity to dismiss the [HintsAndSolutionFragment]. */
/** Allows parent activity to listen to events from [HintsAndSolutionDialogFragment]. */
interface HintsAndSolutionListener {
/** Called when the hints and solution dialog should be dismissed. */
fun dismiss()

/** Called when the hints and solution dialog is opened and hint timers should be paused. */
fun pauseHints()

/** Called when the hints and solution dialog is closed and hint timers should be resumed. */
fun resumeHints()
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ class ExplorationActivity :
getHintsAndSolution()?.dismiss()
}

override fun pauseHints() {
explorationActivityPresenter.pauseHints()
}

override fun resumeHints() {
explorationActivityPresenter.resumeHints()
}

override fun onDefaultFontSizeLoaded(readingTextSize: ReadingTextSize) {
explorationActivityPresenter.loadExplorationFragment(readingTextSize)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,22 @@ class ExplorationActivityPresenter @Inject constructor(
explorationFragment.viewSolution()
}

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

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

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 @@ -85,6 +85,14 @@ class ExplorationFragment : InjectableFragment() {
explorationFragmentPresenter.viewSolution()
}

fun pauseHints() {
explorationFragmentPresenter.pauseHints()
}

fun resumeHints() {
explorationFragmentPresenter.resumeHints()
}

fun getExplorationCheckpointState() = explorationFragmentPresenter.getExplorationCheckpointState()

fun dismissConceptCard() = explorationFragmentPresenter.dismissConceptCard()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ class ExplorationFragmentPresenter @Inject constructor(
getStateFragment()?.viewSolution()
}

fun pauseHints() = getStateFragment()?.pauseHints()

fun resumeHints() = getStateFragment()?.resumeHints()

fun getExplorationCheckpointState() = getStateFragment()?.getExplorationCheckpointState()

private fun getStateFragment(): StateFragment? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ class StateFragment :
stateFragmentPresenter.viewSolution()
}

fun pauseHints() = stateFragmentPresenter.pauseHints()

fun resumeHints() = stateFragmentPresenter.resumeHints()

/**
* Delegates the removal of all [ConceptCardFragment] instances
* to the [StateFragmentPresenter].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,14 @@ class StateFragmentPresenter @Inject constructor(
explorationProgressController.submitSolutionIsViewed()
}

fun pauseHints() {
explorationProgressController.pauseHints()
}

fun resumeHints() {
explorationProgressController.resumeHints()
}

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 @@ -121,6 +121,14 @@ class StateFragmentTestActivity :
getHintsAndSolution()?.dismiss()
}

override fun pauseHints() {
stateFragmentTestActivityPresenter.pauseHints()
}

override fun resumeHints() {
stateFragmentTestActivityPresenter.resumeHints()
}

override fun routeToHintsAndSolution(id: String, helpIndex: HelpIndex) {
if (getHintsAndSolution() == null) {
val hintsAndSolutionFragment =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ class StateFragmentTestActivityPresenter @Inject constructor(

fun revealSolution() = getStateFragment()?.revealSolution()

fun pauseHints() = getStateFragment()?.pauseHints()

fun resumeHints() = getStateFragment()?.resumeHints()

fun deleteCurrentProgressAndStopExploration(isCompletion: Boolean) {
explorationDataController.deleteExplorationProgressById(
profileId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ class QuestionPlayerActivity :

override fun dismiss() = questionPlayerActivityPresenter.dismissHintsAndSolutionDialog()

override fun pauseHints() {
questionPlayerActivityPresenter.pauseHints()
}

override fun resumeHints() {
questionPlayerActivityPresenter.resumeHints()
}

override fun onQuestionStateLoaded(
state: State,
writtenTranslationContext: WrittenTranslationContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,22 @@ class QuestionPlayerActivityPresenter @Inject constructor(
getHintsAndSolutionDialogFragment()?.dismiss()
}

fun pauseHints() {
val questionPlayerFragment =
activity.supportFragmentManager.findFragmentByTag(
TAG_QUESTION_PLAYER_FRAGMENT
) as? QuestionPlayerFragment
questionPlayerFragment?.pauseHints()
}

fun resumeHints() {
val questionPlayerFragment =
activity.supportFragmentManager.findFragmentByTag(
TAG_QUESTION_PLAYER_FRAGMENT
) as? QuestionPlayerFragment
questionPlayerFragment?.resumeHints()
}

fun dismissConceptCard() {
getHintsAndSolutionDialogFragment()?.dismissConceptCard()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ class QuestionPlayerFragment :
questionPlayerFragmentPresenter.revealSolution()
}

fun pauseHints() {
questionPlayerFragmentPresenter.pauseHints()
}

fun resumeHints() {
questionPlayerFragmentPresenter.resumeHints()
}

companion object {

/** Arguments key for [QuestionPlayerFragment]. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ class QuestionPlayerFragmentPresenter @Inject constructor(
subscribeToHintSolution(questionAssessmentProgressController.submitSolutionIsRevealed())
}

fun pauseHints() {
questionAssessmentProgressController.pauseHints()
}

fun resumeHints() {
questionAssessmentProgressController.resumeHints()
}

private fun retrieveArguments(): QuestionPlayerFragmentArguments {
return fragment.requireArguments().getProto(
QuestionPlayerFragment.ARGUMENTS_KEY, QuestionPlayerFragmentArguments.getDefaultInstance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ private const val MOVE_TO_FLASHBACK_STATE_RESULT_PROVIDER_ID =
"ExplorationProgressController.move_to_flashback_state_result"
private const val MOVE_BACK_TO_LATEST_STATE_RESULT_PROVIDER_ID =
"ExplorationProgressController.move_back_to_latest_state_result"
private const val PAUSE_HINTS_RESULT_PROVIDER_ID =
"ExplorationProgressController.pause_hints_result"
private const val RESUME_HINTS_RESULT_PROVIDER_ID =
"ExplorationProgressController.resume_hints_result"

/**
* A default session ID to be used before a session has been initialized.
Expand Down Expand Up @@ -408,6 +412,30 @@ class ExplorationProgressController @Inject constructor(
return moveResultFlow.convertToSessionProvider(MOVE_BACK_TO_LATEST_STATE_RESULT_PROVIDER_ID)
}

/**
* Pauses the hint timer while the hint & solution dialog is open.
*
* @return a [DataProvider] that indicates success/failure of the pause operation
*/
fun pauseHints(): DataProvider<Any?> {
val pauseResultFlow = createAsyncResultStateFlow<Any?>()
val message = ControllerMessage.PauseHints(activeSessionId, pauseResultFlow)
sendCommandForOperation(message) { "Failed to schedule command for pausing hints." }
return pauseResultFlow.convertToSessionProvider(PAUSE_HINTS_RESULT_PROVIDER_ID)
}

/**
* Resumes the hint timer after the hint & solution dialog is dismissed.
*
* @return a [DataProvider] that indicates success/failure of the resume operation
*/
fun resumeHints(): DataProvider<Any?> {
val resumeResultFlow = createAsyncResultStateFlow<Any?>()
val message = ControllerMessage.ResumeHints(activeSessionId, resumeResultFlow)
sendCommandForOperation(message) { "Failed to schedule command for resuming hints." }
return resumeResultFlow.convertToSessionProvider(RESUME_HINTS_RESULT_PROVIDER_ID)
}

/**
* Returns a [DataProvider] monitoring the current [EphemeralState] the learner is currently
* viewing.
Expand Down Expand Up @@ -584,6 +612,10 @@ class ExplorationProgressController @Inject constructor(
controllerState.moveToNextStateImpl(message.callbackFlow)
is ControllerMessage.LogUpdatedHelpIndex ->
controllerState.maybeLogUpdatedHelpIndex(message.helpIndex, activeSessionId)
is ControllerMessage.PauseHints ->
controllerState.pauseHintsImpl(message.callbackFlow)
is ControllerMessage.ResumeHints ->
controllerState.resumeHintsImpl(message.callbackFlow)
is ControllerMessage.ProcessSavedCheckpointResult -> {
controllerState.processSaveCheckpointResult(
message.profileId,
Expand Down Expand Up @@ -980,6 +1012,34 @@ class ExplorationProgressController @Inject constructor(
}
}

private suspend fun ControllerState.pauseHintsImpl(
pauseResultFlow: MutableStateFlow<AsyncResult<Any?>>
) {
tryOperation(pauseResultFlow, recomputeState = false) {
check(explorationProgress.playStage != NOT_PLAYING) {
"Cannot pause hints if an exploration is not being played."
}
check(explorationProgress.playStage != LOADING_EXPLORATION) {
"Cannot pause hints while the exploration is being loaded."
}
hintHandler.pauseHints()
}
}

private suspend fun ControllerState.resumeHintsImpl(
resumeResultFlow: MutableStateFlow<AsyncResult<Any?>>
) {
tryOperation(resumeResultFlow, recomputeState = false) {
check(explorationProgress.playStage != NOT_PLAYING) {
"Cannot resume hints if an exploration is not being played."
}
check(explorationProgress.playStage != LOADING_EXPLORATION) {
"Cannot resume hints while the exploration is being loaded."
}
hintHandler.resumeHints()
}
}

private fun ControllerState.maybeLogViewedHint(
activeSessionId: String,
hintIndex: Int
Expand Down Expand Up @@ -1608,6 +1668,18 @@ class ExplorationProgressController @Inject constructor(
override val callbackFlow: MutableStateFlow<AsyncResult<Any?>>
) : ControllerMessage<Any?>()

/** [ControllerMessage] to pause the hint timer while the hint dialog is open. */
data class PauseHints(
override val sessionId: String,
override val callbackFlow: MutableStateFlow<AsyncResult<Any?>>
) : ControllerMessage<Any?>()

/** [ControllerMessage] to resume the hint timer after the hint dialog is dismissed. */
data class ResumeHints(
override val sessionId: String,
override val callbackFlow: MutableStateFlow<AsyncResult<Any?>>
) : ControllerMessage<Any?>()

/**
* [ControllerMessage] to indicate that the session's current partial completion progress should
* be saved to disk.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ interface HintHandler {
*/
suspend fun navigateBackToLatestPendingState()

/**
* Pauses the hint timer while the hint & solution dialog is open. Any scheduled hint coroutine
* will be cancelled and the remaining delay recorded so it can be resumed later.
*
* This is a no-op if no timer is currently scheduled (e.g. the state has no hints, or the timer
* has already fired).
*/
suspend fun pauseHints()

/**
* Resumes the hint timer after the hint & solution dialog is dismissed, scheduling a new hint
* task with the remaining time that was left when [pauseHints] was called.
*
* This is a no-op if [pauseHints] has not previously been called or if no timer was active at
* the time [pauseHints] was called.
*/
suspend fun resumeHints()

/**
* Returns a [StateFlow] of the [HelpIndex] corresponding to the current pending state which can
* be used to actively monitor the index state, if desired.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class HintHandlerDebugImpl private constructor() : HintHandler {

override suspend fun navigateBackToLatestPendingState() {}

// The debug impl always reveals everything immediately; pause/resume are no-ops.
override suspend fun pauseHints() {}

override suspend fun resumeHints() {}

override fun getCurrentHelpIndex(): StateFlow<HelpIndex> = helpIndexFlow

private fun recomputeHelpIndex(pendingState: State) {
Expand Down
Loading
Loading