diff --git a/app/BUILD.bazel b/app/BUILD.bazel index c74dca27553..e1cbcc7c091 100644 --- a/app/BUILD.bazel +++ b/app/BUILD.bazel @@ -394,6 +394,7 @@ VIEW_MODELS = [ # keep sorted VIEWS_WITH_RESOURCE_IMPORTS = [ + "src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt", "src/main/java/org/oppia/android/app/customview/LessonThumbnailImageView.kt", "src/main/java/org/oppia/android/app/customview/SegmentedCircularProgressView.kt", "src/main/java/org/oppia/android/app/customview/VerticalDashedLineView.kt", @@ -630,6 +631,7 @@ kt_android_library( ":snap_helper", ":view_models", "//app/src/main/java/org/oppia/android/app/shim:view_binding_shim", + "//app/src/main/java/org/oppia/android/app/utility/lifecycle:lifecycle_safe_timer_factory", "//app/src/main/java/org/oppia/android/app/view:view_component_factory", "//app/src/main/java/org/oppia/android/app/view:view_scope", "//third_party:androidx_appcompat_appcompat", diff --git a/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt b/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt new file mode 100644 index 00000000000..da4235fd2f7 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt @@ -0,0 +1,124 @@ +package org.oppia.android.app.customview + +import android.content.Context +import android.util.AttributeSet +import android.view.animation.AnimationUtils +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.LiveData +import org.oppia.android.R +import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory +import org.oppia.android.app.view.ViewComponentFactory +import org.oppia.android.app.view.ViewComponentImpl +import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.util.platformparameter.EnableContinueButtonAnimation +import org.oppia.android.util.platformparameter.PlatformParameterValue +import org.oppia.android.util.system.OppiaClock +import javax.inject.Inject + +/** A custom [AppCompatButton] used to show continue button animations. */ +class ContinueButtonView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.style.StateButtonActive +) : androidx.appcompat.widget.AppCompatButton(context, attrs, defStyleAttr) { + + @field:[Inject EnableContinueButtonAnimation] + lateinit var enableContinueButtonAnimation: PlatformParameterValue + @Inject lateinit var fragment: Fragment + @Inject lateinit var oppiaClock: OppiaClock + @Inject lateinit var lifecycleSafeTimerFactory: LifecycleSafeTimerFactory + @Inject lateinit var oppiaLogger: OppiaLogger + + private var shouldAnimateContinueButtonLateinit: Boolean? = null + private val shouldAnimateContinueButton: Boolean + get() = checkNotNull(shouldAnimateContinueButtonLateinit) { + "Expected shouldAnimateContinueButtonLateinit to be initialized by this point." + } + + private var continueButtonAnimationTimestampMsLateinit: Long? = null + private val continueButtonAnimationTimestampMs: Long + get() = checkNotNull(continueButtonAnimationTimestampMsLateinit) { + "Expected continueButtonAnimationTimestampMsLateinit to be initialized by this point." + } + + private val hasAnimationTimerFinished: Boolean + get() = continueButtonAnimationTimestampMs < oppiaClock.getCurrentTimeMs() + + private var animationStartTimer: LiveData? = null + private var currentAnimationReuseCount = 0 + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + val viewComponentFactory = + FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl + viewComponent.inject(this) + maybeInitializeAnimation() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + // Make sure state can't leak across rebinding boundaries (since this view may be reused). + cancelOngoingTimer() + } + + /** Sets whether the view should animate to catch a user's attention. */ + fun setShouldAnimateContinueButton(shouldAnimateContinueButton: Boolean) { + shouldAnimateContinueButtonLateinit = shouldAnimateContinueButton + maybeInitializeAnimation() + } + + /** + * Sets when, in clock time, the animation controlled by [setShouldAnimateContinueButton] should + * play. + */ + fun setContinueButtonAnimationTimestampMs(continueButtonAnimationTimestampMs: Long) { + continueButtonAnimationTimestampMsLateinit = continueButtonAnimationTimestampMs + maybeInitializeAnimation() + } + + private fun maybeInitializeAnimation() { + if (::oppiaClock.isInitialized && + shouldAnimateContinueButtonLateinit != null && + continueButtonAnimationTimestampMsLateinit != null + ) { + when { + !shouldAnimateContinueButton -> clearAnimation() + hasAnimationTimerFinished -> startAnimating() + else -> { + val timeLeftToAnimate = continueButtonAnimationTimestampMs - oppiaClock.getCurrentTimeMs() + startAnimatingWithDelay(delayMs = timeLeftToAnimate) + } + } + } + } + + private fun startAnimatingWithDelay(delayMs: Long) { + cancelOngoingTimer() + val sequenceNumber = currentAnimationReuseCount + lifecycleSafeTimerFactory.createTimer(delayMs).observe(fragment) { + // Only play the animation if it's still valid to do so (since the view may have been recycled + // for a new context that may not want the animation to play). + if (sequenceNumber == currentAnimationReuseCount) { + startAnimating() + } + } + } + + private fun cancelOngoingTimer() { + currentAnimationReuseCount++ + animationStartTimer?.let { + it.removeObservers(fragment) + animationStartTimer = null + } + } + + private fun startAnimating() { + val animation = AnimationUtils.loadAnimation(context, R.anim.scale_button_size) + if (enableContinueButtonAnimation.value) { + startAnimation(animation) + } + } +} diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt index 1f9a6116436..e4167a6f9ad 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt @@ -35,8 +35,8 @@ import org.oppia.android.app.player.state.ConfettiConfig.MINI_CONFETTI_BURST import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListener import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionWithSavedProgressListener import org.oppia.android.app.topic.conceptcard.ConceptCardFragment.Companion.CONCEPT_CARD_DIALOG_FRAGMENT_TAG -import org.oppia.android.app.utility.LifecycleSafeTimerFactory import org.oppia.android.app.utility.SplitScreenManager +import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory import org.oppia.android.app.viewmodel.ViewModelProvider import org.oppia.android.databinding.StateFragmentBinding import org.oppia.android.domain.exploration.ExplorationProgressController diff --git a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt index bac406d7fda..3163747034b 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt @@ -67,7 +67,7 @@ import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.conceptcard.ConceptCardFragment import org.oppia.android.app.topic.conceptcard.ConceptCardFragment.Companion.CONCEPT_CARD_DIALOG_FRAGMENT_TAG import org.oppia.android.app.translation.AppLanguageResourceHandler -import org.oppia.android.app.utility.LifecycleSafeTimerFactory +import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory import org.oppia.android.databinding.ContentItemBinding import org.oppia.android.databinding.ContinueInteractionItemBinding import org.oppia.android.databinding.ContinueNavigationButtonItemBinding @@ -226,12 +226,18 @@ class StatePlayerRecyclerViewAssembler private constructor( if (playerFeatureSet.interactionSupport) { val interactionItemList = if (isSplitView) extraInteractionPendingItemList else conversationPendingItemList + val timeToStartNoticeAnimationMs = if (interaction.id == "Continue") { + ephemeralState.continueButtonAnimationTimestampMs.takeIf { + ephemeralState.showContinueButtonAnimation + } + } else null addInteractionForPendingState( interactionItemList, interaction, hasPreviousState, gcsEntityId, - ephemeralState.writtenTranslationContext + ephemeralState.writtenTranslationContext, + timeToStartNoticeAnimationMs ) } } else if (ephemeralState.stateTypeCase == StateTypeCase.COMPLETED_STATE) { @@ -295,7 +301,9 @@ class StatePlayerRecyclerViewAssembler private constructor( hasPreviousState, canContinueToNextState, hasGeneralContinueButton, - isTerminalState + isTerminalState, + shouldAnimateContinueButton = ephemeralState.showContinueButtonAnimation, + continueButtonAnimationTimestampMs = ephemeralState.continueButtonAnimationTimestampMs ) return Pair(conversationPendingItemList, extraInteractionPendingItemList) } @@ -305,7 +313,8 @@ class StatePlayerRecyclerViewAssembler private constructor( interaction: Interaction, hasPreviousButton: Boolean, gcsEntityId: String, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ) { val interactionViewModelFactory = interactionViewModelFactoryMap.getValue(interaction.id) pendingItemList += interactionViewModelFactory.create( @@ -316,7 +325,8 @@ class StatePlayerRecyclerViewAssembler private constructor( fragment as InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton, isSplitView.get()!!, - writtenTranslationContext + writtenTranslationContext, + timeToStartNoticeAnimationMs ) } @@ -592,7 +602,9 @@ class StatePlayerRecyclerViewAssembler private constructor( hasPreviousState: Boolean, canContinueToNextState: Boolean, hasGeneralContinueButton: Boolean, - stateIsTerminal: Boolean + stateIsTerminal: Boolean, + shouldAnimateContinueButton: Boolean, + continueButtonAnimationTimestampMs: Long ) { val hasPreviousButton = playerFeatureSet.backwardNavigation && hasPreviousState when { @@ -600,7 +612,9 @@ class StatePlayerRecyclerViewAssembler private constructor( addContinueNavigation( conversationPendingItemList, extraInteractionPendingItemList, - hasPreviousButton + hasPreviousButton, + shouldAnimateContinueButton, + continueButtonAnimationTimestampMs ) } canContinueToNextState && playerFeatureSet.forwardNavigation -> { @@ -725,7 +739,9 @@ class StatePlayerRecyclerViewAssembler private constructor( private fun addContinueNavigation( conversationPendingItemList: MutableList, extraInteractionPendingItemList: MutableList, - hasPreviousButton: Boolean + hasPreviousButton: Boolean, + shouldAnimateContinueButton: Boolean, + continueButtonAnimationTimestampMs: Long ) { val targetList = if (isSplitView.get()!!) extraInteractionPendingItemList else conversationPendingItemList @@ -735,7 +751,9 @@ class StatePlayerRecyclerViewAssembler private constructor( hasConversationView, previousNavigationButtonListener, fragment as ContinueNavigationButtonListener, - isSplitView.get()!! + isSplitView.get()!!, + shouldAnimateContinueButton, + continueButtonAnimationTimestampMs ) if (isSplitView.get()!!) { // "previous button" should appear in the conversation recycler view only diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueInteractionViewModel.kt index d79be12f560..2200ad829a1 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueInteractionViewModel.kt @@ -27,9 +27,10 @@ class ContinueInteractionViewModel private constructor( val hasPreviousButton: Boolean, val previousNavigationButtonListener: PreviousNavigationButtonListener, val isSplitView: Boolean, - private val writtenTranslationContext: WrittenTranslationContext + private val writtenTranslationContext: WrittenTranslationContext, + val shouldAnimateContinueButton: Boolean, + val continueButtonAnimationTimestampMs: Long ) : StateItemViewModel(ViewType.CONTINUE_INTERACTION), InteractionAnswerHandler { - override fun isExplicitAnswerSubmissionRequired(): Boolean = false override fun isAutoNavigating(): Boolean = true @@ -46,9 +47,7 @@ class ContinueInteractionViewModel private constructor( } /** Implementation of [StateItemViewModel.InteractionItemFactory] for this view model. */ - class FactoryImpl @Inject constructor( - private val fragment: Fragment - ) : InteractionItemFactory { + class FactoryImpl @Inject constructor(private val fragment: Fragment) : InteractionItemFactory { override fun create( entityId: String, hasConversationView: Boolean, @@ -57,7 +56,8 @@ class ContinueInteractionViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return ContinueInteractionViewModel( interactionAnswerReceiver, @@ -65,7 +65,9 @@ class ContinueInteractionViewModel private constructor( hasPreviousButton, fragment as PreviousNavigationButtonListener, isSplitView, - writtenTranslationContext + writtenTranslationContext, + shouldAnimateContinueButton = timeToStartNoticeAnimationMs != null, + continueButtonAnimationTimestampMs = timeToStartNoticeAnimationMs ?: 0L ) } } diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueNavigationButtonViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueNavigationButtonViewModel.kt index 2f668b3848a..ba27fc2287d 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueNavigationButtonViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueNavigationButtonViewModel.kt @@ -14,5 +14,7 @@ class ContinueNavigationButtonViewModel( val hasConversationView: Boolean, val previousNavigationButtonListener: PreviousNavigationButtonListener, val continueNavigationButtonListener: ContinueNavigationButtonListener, - val isSplitView: Boolean + val isSplitView: Boolean, + val shouldAnimateContinueButton: Boolean, + val continueButtonAnimationTimestampMs: Long ) : StateItemViewModel(ViewType.CONTINUE_NAVIGATION_BUTTON) diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt index 1509d9a295b..6cd22508141 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt @@ -203,7 +203,8 @@ class DragAndDropSortInteractionViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return DragAndDropSortInteractionViewModel( entityId, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt index 54ee1566a38..22d42a74744 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt @@ -141,7 +141,8 @@ class FractionInteractionViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return FractionInteractionViewModel( interaction, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt index 47b6a5e4569..0c4029e940e 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt @@ -103,7 +103,8 @@ class ImageRegionSelectionInteractionViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return ImageRegionSelectionInteractionViewModel( entityId, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt index 77687c70a5d..5d0822fae6c 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt @@ -230,7 +230,8 @@ class MathExpressionInteractionsViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return MathExpressionInteractionsViewModel( interaction, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt index 70deff47224..93bdddde6f0 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt @@ -102,7 +102,8 @@ class NumericInputViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return NumericInputViewModel( hasConversationView, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt index 749151c4c40..49f64619702 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt @@ -140,7 +140,8 @@ class RatioExpressionInputInteractionViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return RatioExpressionInputInteractionViewModel( interaction, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionViewModel.kt index 21976f2a971..c204e188765 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionViewModel.kt @@ -171,7 +171,8 @@ class SelectionInteractionViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return SelectionInteractionViewModel( entityId, diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/StateItemViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/StateItemViewModel.kt index bce47761e47..3d93249cf7c 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/StateItemViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/StateItemViewModel.kt @@ -7,7 +7,8 @@ import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiv import org.oppia.android.app.viewmodel.ObservableViewModel /** - * The root [ObservableViewModel] for all individual items that may be displayed in the state fragment recycler view. + * The root [ObservableViewModel] for all individual items that may be displayed in the state + * fragment recycler view. */ abstract class StateItemViewModel(val viewType: ViewType) : ObservableViewModel() { @@ -40,9 +41,13 @@ abstract class StateItemViewModel(val viewType: ViewType) : ObservableViewModel( interface InteractionItemFactory { /** * Returns a new [StateItemViewModel] corresponding to this interaction with the GCS entity ID, - * the [Interaction] object corresponding to the interaction view, a receiver for answers if this - * interaction pushes answers, and whether there's a previous button enabled (only relevant for - * navigation-based interactions). + * the [Interaction] object corresponding to the interaction view, a receiver for answers if + * this interaction pushes answers, and whether there's a previous button enabled (only relevant + * for navigation-based interactions). + * + * @param timeToStartNoticeAnimationMs the milliseconds at which the implementation should start + * its "take notice" animation for the user, if it has one. When null, the animation should + * never be shown. */ fun create( entityId: String, @@ -52,7 +57,8 @@ abstract class StateItemViewModel(val viewType: ViewType) : ObservableViewModel( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel } } diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt index 9395c23cdb7..b3bdbba2ac0 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt @@ -107,7 +107,8 @@ class TextInputViewModel private constructor( answerErrorReceiver: InteractionAnswerErrorOrAvailabilityCheckReceiver, hasPreviousButton: Boolean, isSplitView: Boolean, - writtenTranslationContext: WrittenTranslationContext + writtenTranslationContext: WrittenTranslationContext, + timeToStartNoticeAnimationMs: Long? ): StateItemViewModel { return TextInputViewModel( interaction, diff --git a/app/src/main/java/org/oppia/android/app/player/state/testing/StateFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/player/state/testing/StateFragmentTestActivity.kt index 29cb26a42e4..fcecfe7d311 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/testing/StateFragmentTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/testing/StateFragmentTestActivity.kt @@ -97,6 +97,10 @@ class StateFragmentTestActivity : stateFragmentTestActivityPresenter.scrollToTop() } + fun stopExploration(isCompletion: Boolean) { + stateFragmentTestActivityPresenter.stopExploration(isCompletion) + } + override fun dismiss() {} override fun routeToHintsAndSolution( diff --git a/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt index 3be8ccfce7b..a3e262baf63 100644 --- a/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt @@ -10,8 +10,8 @@ import org.oppia.android.R import org.oppia.android.app.home.HomeActivity import org.oppia.android.app.model.ProfileId import org.oppia.android.app.translation.AppLanguageResourceHandler -import org.oppia.android.app.utility.LifecycleSafeTimerFactory import org.oppia.android.app.utility.TextInputEditTextHelper.Companion.onTextChanged +import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory import org.oppia.android.app.viewmodel.ViewModelProvider import org.oppia.android.databinding.PinPasswordActivityBinding import org.oppia.android.domain.profile.ProfileManagementController diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt index 5cbbc7df08c..cfdf37874bf 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt @@ -16,7 +16,7 @@ import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragme import org.oppia.android.app.onboarding.OnboardingActivity import org.oppia.android.app.profile.ProfileChooserActivity import org.oppia.android.app.translation.AppLanguageLocaleHandler -import org.oppia.android.app.utility.LifecycleSafeTimerFactory +import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory import org.oppia.android.databinding.SplashActivityBinding import org.oppia.android.domain.locale.LocaleController import org.oppia.android.domain.onboarding.AppStartupStateController diff --git a/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt index 1fc9c3d6042..5e95bc289c7 100644 --- a/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt @@ -163,7 +163,8 @@ class InputInteractionViewTestActivity : answerErrorReceiver = this@InputInteractionViewTestActivity, hasPreviousButton = false, isSplitView = false, - writtenTranslationContext + writtenTranslationContext, + timeToStartNoticeAnimationMs = null ) as T } diff --git a/app/src/main/java/org/oppia/android/app/utility/lifecycle/BUILD.bazel b/app/src/main/java/org/oppia/android/app/utility/lifecycle/BUILD.bazel new file mode 100644 index 00000000000..9e47fd3ff48 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/utility/lifecycle/BUILD.bazel @@ -0,0 +1,22 @@ +""" +Constructs for setting up lifecycle safe timer factory for injection in the Dagger graph. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") + +kt_android_library( + name = "lifecycle_safe_timer_factory", + srcs = [ + "LifecycleSafeTimerFactory.kt", + ], + visibility = ["//app:app_visibility"], + deps = [ + "//third_party:androidx_lifecycle_lifecycle-livedata-core", + "//third_party:javax_inject_javax_inject", + "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-core", + "//utility/src/main/java/org/oppia/android/util/threading:annotations", + ], +) + +dagger_rules() diff --git a/app/src/main/java/org/oppia/android/app/utility/LifecycleSafeTimerFactory.kt b/app/src/main/java/org/oppia/android/app/utility/lifecycle/LifecycleSafeTimerFactory.kt similarity index 98% rename from app/src/main/java/org/oppia/android/app/utility/LifecycleSafeTimerFactory.kt rename to app/src/main/java/org/oppia/android/app/utility/lifecycle/LifecycleSafeTimerFactory.kt index 70c71ec8d81..52d766e0a64 100644 --- a/app/src/main/java/org/oppia/android/app/utility/LifecycleSafeTimerFactory.kt +++ b/app/src/main/java/org/oppia/android/app/utility/lifecycle/LifecycleSafeTimerFactory.kt @@ -1,4 +1,4 @@ -package org.oppia.android.app.utility +package org.oppia.android.app.utility.lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData diff --git a/app/src/main/java/org/oppia/android/app/view/ViewComponentImpl.kt b/app/src/main/java/org/oppia/android/app/view/ViewComponentImpl.kt index 5be05546d1e..e950b36d89b 100644 --- a/app/src/main/java/org/oppia/android/app/view/ViewComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/view/ViewComponentImpl.kt @@ -3,6 +3,7 @@ package org.oppia.android.app.view import android.view.View import dagger.BindsInstance import dagger.Subcomponent +import org.oppia.android.app.customview.ContinueButtonView import org.oppia.android.app.customview.LessonThumbnailImageView import org.oppia.android.app.customview.SegmentedCircularProgressView import org.oppia.android.app.home.promotedlist.ComingSoonTopicsListView @@ -27,6 +28,7 @@ interface ViewComponentImpl : ViewComponent { } fun inject(comingSoonTopicsListView: ComingSoonTopicsListView) + fun inject(continueButtonView: ContinueButtonView) fun inject(selectionInteractionView: SelectionInteractionView) fun inject(dragDropSortInteractionView: DragDropSortInteractionView) fun inject(imageRegionSelectionInteractionView: ImageRegionSelectionInteractionView) diff --git a/app/src/main/res/anim/scale_button_size.xml b/app/src/main/res/anim/scale_button_size.xml new file mode 100644 index 00000000000..39a23b55478 --- /dev/null +++ b/app/src/main/res/anim/scale_button_size.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/continue_interaction_item.xml b/app/src/main/res/layout/continue_interaction_item.xml index 12a500b82cc..742d0af6f16 100644 --- a/app/src/main/res/layout/continue_interaction_item.xml +++ b/app/src/main/res/layout/continue_interaction_item.xml @@ -12,6 +12,7 @@ - -