Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #5069: Add a "hint/solution viewed" event to complement the existing "offered"/"unlocked" events. #5298

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
453dad4
hint viewed event log
Vishwajith-Shettigar Jan 13, 2024
4daa3fc
hint viewed event log
Vishwajith-Shettigar Jan 14, 2024
6c1467c
doc fix
Vishwajith-Shettigar Jan 14, 2024
26778d7
solution view event
Vishwajith-Shettigar Jan 14, 2024
5ee0fef
solution view event
Vishwajith-Shettigar Jan 14, 2024
bd16abc
added kdoc
Vishwajith-Shettigar Jan 14, 2024
eebab9a
TEST FILE CHECK issue fixed
Vishwajith-Shettigar Jan 14, 2024
a407224
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Jan 14, 2024
056b2ed
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Jan 29, 2024
d7b54b1
refinied code
Vishwajith-Shettigar Feb 2, 2024
f1cdf9a
test fix
Vishwajith-Shettigar Feb 2, 2024
8e29552
test fix
Vishwajith-Shettigar Feb 2, 2024
4358abc
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Mar 5, 2024
7089dcd
support event log for hint and solution views
Vishwajith-Shettigar Mar 6, 2024
25d284f
klint fix
Vishwajith-Shettigar Mar 6, 2024
f723644
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Mar 13, 2024
a74c102
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Apr 4, 2024
32683f1
remove redundant callback
Vishwajith-Shettigar Apr 6, 2024
e77bc0e
Added tests
Vishwajith-Shettigar Apr 6, 2024
c41fda3
fix ktlint
Vishwajith-Shettigar Apr 6, 2024
0a3f0b0
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Apr 8, 2024
267bb13
rename accesshint to revealhint
Vishwajith-Shettigar Apr 16, 2024
56b9cc4
klint
Vishwajith-Shettigar Apr 16, 2024
a1b556d
rename accesssolution to revealsolution
Vishwajith-Shettigar Apr 16, 2024
b6a28a8
rename accesssolution to revealsolution
Vishwajith-Shettigar Apr 16, 2024
4753d7c
resolve conflict
Vishwajith-Shettigar Apr 16, 2024
31e8491
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Apr 16, 2024
cd6f87f
resolve conflict
Vishwajith-Shettigar Apr 16, 2024
5dbf54e
fix typo
Vishwajith-Shettigar Apr 16, 2024
641923a
test fix
Vishwajith-Shettigar Apr 16, 2024
cf8bc5d
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Apr 17, 2024
6ef7cdd
Remove extra space
Vishwajith-Shettigar Apr 25, 2024
2344bdb
Addressed comments
Vishwajith-Shettigar Jun 6, 2024
5b78026
Add tests in EventBundleCreatorTest
Vishwajith-Shettigar Jun 6, 2024
c9f447e
klint
Vishwajith-Shettigar Jun 6, 2024
c485834
Resolved conflicts
Vishwajith-Shettigar Jun 6, 2024
6551d27
lexicographic order fix
Vishwajith-Shettigar Jun 6, 2024
67e6b8a
Added tests in KenyaAlphaEventBundleCreatorTest
Vishwajith-Shettigar Jun 6, 2024
126a337
hintviewlog tests
Vishwajith-Shettigar Jun 8, 2024
31e3cc0
klint fix
Vishwajith-Shettigar Jun 8, 2024
727c06a
Solutionviewlog tests
Vishwajith-Shettigar Jun 8, 2024
818898b
Addressed comments
Vishwajith-Shettigar Jun 23, 2024
49745ad
Addressed comments
Vishwajith-Shettigar Jun 23, 2024
2376cce
rename some tests name
Vishwajith-Shettigar Jun 23, 2024
2e36bf9
resolve conflicts
Vishwajith-Shettigar Jun 24, 2024
f1fc6d1
Addressed comment
Vishwajith-Shettigar Jun 27, 2024
9f66176
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Jun 27, 2024
6d543c0
Merge branch 'develop' into hint-solution-view-event
adhiamboperes Jun 27, 2024
15edfba
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Jun 29, 2024
7bce27e
Merge branch 'develop' into hint-solution-view-event
Vishwajith-Shettigar Jun 30, 2024
0b4d0cc
Merge branch 'develop' into hint-solution-view-event
adhiamboperes Jul 1, 2024
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 @@ -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
Loading