forked from oppia/oppia-android
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!-- READ ME FIRST: Please fill in the explanation section below and check off every point from the Essential Checklist! --> ## Explanation <!-- - Explain what your PR does. If this PR fixes an existing bug, please include - "Fixes #bugnum:" in the explanation so that GitHub can auto-close the issue - when this PR is merged. --> Fixes oppia#4445 This PR is part of the GSoC project: Interactive Onboarding Flow which fixes oppia#4445. It does so by creating a new custom view which connects to the `ContinueInteractionItemViewModel`, which receives a flag to start animating from the explorationProgressController. We wait for 45 seconds when the first card of a lesson is opened by the user, and then start the animation for the continue button. ## Essential Checklist <!-- Please tick the relevant boxes by putting an "x" in them. --> - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only <!-- Delete these section if this PR does not include UI-related changes. --> If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-(A11y)-Guide)) - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing https://user-images.githubusercontent.com/64526117/202579860-643e2d20-b969-438d-be86-f690630b8278.mp4 Co-authored-by: Ben Henning <[email protected]> Co-authored-by: Ben Henning <[email protected]>
- Loading branch information
1 parent
7cb38ff
commit 6f26b9e
Showing
47 changed files
with
809 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Boolean> | ||
@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<Any>? = null | ||
private var currentAnimationReuseCount = 0 | ||
|
||
override fun onAttachedToWindow() { | ||
super.onAttachedToWindow() | ||
val viewComponentFactory = | ||
FragmentManager.findFragment<Fragment>(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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.