From 8e5cb491a6936d93897ada12932d26fc5c633872 Mon Sep 17 00:00:00 2001
From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com>
Date: Wed, 19 Jun 2024 00:26:12 +0300
Subject: [PATCH] Fix Part of #4938: Introduce New App Language Selection
Screen for onboarding (#5373)
## Explanation
Fixes Part of #4938: Introuduces new app language selection screen.
This PR introduces the layout files and the presenter for displaying the
language functionality, along with associated test cases.
I have modified the custom view `SurveyOnboardingBackgroundView` to make
it generic and reusable in the new layouts.
These changes include both darkmode support and alternate screen size
and orientation layouts, as per figma mocks.
## Essential Checklist
- [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
||||
|--- |---|---|
||Lightmode Portrait|Darkmode & Landscape|
|Mobile
**xxhdpi**|![Screenshot_1711564671](https://github.com/oppia/oppia-android/assets/59600948/7399cad6-709c-4aa0-a2e8-5c2aeceb36d7)|![Screenshot_1712156819](https://github.com/oppia/oppia-android/assets/59600948/b6cace78-28ed-452a-b5d2-c8ce1e0e2567)|
|Mobile
**mdpi**|![Screenshot_1712156777](https://github.com/oppia/oppia-android/assets/59600948/3ce79b76-f8d0-44e2-ac2f-329c2cc9f598)|![Screenshot_1712156762](https://github.com/oppia/oppia-android/assets/59600948/0c0cba3f-8d5c-4aa6-9fab-7b902de08a0e)|
|Tablet
**xxhdpi**|![Screenshot_1711554842](https://github.com/oppia/oppia-android/assets/59600948/58ac2b67-ecec-439e-91f4-dc1dde2f2918)|![Screenshot_1712151724](https://github.com/oppia/oppia-android/assets/59600948/c8eafa6a-8550-449b-a79d-76ec961c24bc)|
|Tablet
**hdpi**|![Screenshot_1711563235](https://github.com/oppia/oppia-android/assets/59600948/4e4ae865-1e8c-4f8f-879e-8a46fe795ab4)|![Screenshot_1712151535](https://github.com/oppia/oppia-android/assets/59600948/52d07d1a-5146-41ce-b4da-597e22c4a529)|
---
app/BUILD.bazel | 3 +-
app/src/main/AndroidManifest.xml | 3 +
.../app/activity/ActivityComponentImpl.kt | 2 +
...undView.kt => OppiaCurveBackgroundView.kt} | 83 +++---
.../app/databinding/ColorBindingAdapters.java | 14 ++
.../app/onboarding/OnboardingFragment.kt | 15 +-
.../onboarding/OnboardingFragmentPresenter.kt | 221 +---------------
.../OnboardingFragmentPresenterV1.kt | 237 ++++++++++++++++++
.../ColorBindingAdaptersTestActivity.kt | 26 ++
.../ColorBindingAdaptersTestFragment.kt | 24 ++
.../android/app/view/ViewComponentImpl.kt | 4 +-
.../main/res/drawable/dropdown_background.xml | 8 +
.../drawable/ic_language_icon_black_24dp.xml | 10 +
app/src/main/res/drawable/otter.xml | 130 ++++++++++
...arding_app_language_selection_fragment.xml | 135 ++++++++++
.../survey_welcome_dialog_fragment.xml | 3 +-
...arding_app_language_selection_fragment.xml | 148 +++++++++++
...arding_app_language_selection_fragment.xml | 146 +++++++++++
.../survey_welcome_dialog_fragment.xml | 3 +-
.../activity_color_binding_adapters_test.xml | 17 ++
.../color_binding_adapters_test_fragment.xml | 5 +
...arding_app_language_selection_fragment.xml | 140 +++++++++++
.../layout/survey_outro_dialog_fragment.xml | 3 +-
.../layout/survey_welcome_dialog_fragment.xml | 3 +-
.../main/res/values-night/color_palette.xml | 5 +-
app/src/main/res/values/color_palette.xml | 3 +
app/src/main/res/values/component_colors.xml | 3 +
app/src/main/res/values/dimens.xml | 29 +++
app/src/main/res/values/strings.xml | 10 +
app/src/main/res/values/styles.xml | 52 ++++
.../oppia/android/app/databinding/BUILD.bazel | 36 +++
.../databinding/ColorBindingAdaptersTest.kt | 223 ++++++++++++++++
.../app/onboarding/OnboardingFragmentTest.kt | 156 +++++++++++-
.../accessibility_label_exemptions.textproto | 1 +
.../file_content_validation_checks.textproto | 3 +-
.../assets/kdoc_validity_exemptions.textproto | 3 +-
scripts/assets/test_file_exemptions.textproto | 5 +-
37 files changed, 1651 insertions(+), 261 deletions(-)
rename app/src/main/java/org/oppia/android/app/customview/{SurveyOnboardingBackgroundView.kt => OppiaCurveBackgroundView.kt} (52%)
create mode 100644 app/src/main/java/org/oppia/android/app/databinding/ColorBindingAdapters.java
create mode 100644 app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenterV1.kt
create mode 100644 app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestActivity.kt
create mode 100644 app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestFragment.kt
create mode 100644 app/src/main/res/drawable/dropdown_background.xml
create mode 100644 app/src/main/res/drawable/ic_language_icon_black_24dp.xml
create mode 100644 app/src/main/res/drawable/otter.xml
create mode 100644 app/src/main/res/layout-land/onboarding_app_language_selection_fragment.xml
create mode 100644 app/src/main/res/layout-sw600dp-land/onboarding_app_language_selection_fragment.xml
create mode 100644 app/src/main/res/layout-sw600dp-port/onboarding_app_language_selection_fragment.xml
create mode 100644 app/src/main/res/layout/activity_color_binding_adapters_test.xml
create mode 100644 app/src/main/res/layout/color_binding_adapters_test_fragment.xml
create mode 100644 app/src/main/res/layout/onboarding_app_language_selection_fragment.xml
create mode 100644 app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt
diff --git a/app/BUILD.bazel b/app/BUILD.bazel
index 1e4620c4821..cfa6c0b3c47 100644
--- a/app/BUILD.bazel
+++ b/app/BUILD.bazel
@@ -408,9 +408,9 @@ VIEWS_WITH_RESOURCE_IMPORTS = [
"src/main/java/org/oppia/android/app/customview/ChapterNotStartedContainerConstraintLayout.kt",
"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/OppiaCurveBackgroundView.kt",
"src/main/java/org/oppia/android/app/customview/PromotedStoryCardView.kt",
"src/main/java/org/oppia/android/app/customview/SegmentedCircularProgressView.kt",
- "src/main/java/org/oppia/android/app/customview/SurveyOnboardingBackgroundView.kt",
"src/main/java/org/oppia/android/app/customview/VerticalDashedLineView.kt",
"src/main/java/org/oppia/android/app/survey/SurveyMultipleChoiceOptionView.kt",
"src/main/java/org/oppia/android/app/survey/SurveyNpsItemOptionView.kt",
@@ -485,6 +485,7 @@ BINDING_ADAPTERS_WITH_RESOURCE_IMPORTS = [
BINDING_ADAPTERS = [
"src/main/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdapters.java",
"src/main/java/org/oppia/android/app/databinding/CircularProgressIndicatorAdapters.java",
+ "src/main/java/org/oppia/android/app/databinding/ColorBindingAdapters.java",
"src/main/java/org/oppia/android/app/databinding/ConstraintLayoutAdapters.java",
"src/main/java/org/oppia/android/app/databinding/EditTextBindingAdapters.java",
"src/main/java/org/oppia/android/app/databinding/GuidelineBindingAdapters.java",
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 41d1ce55918..b85115c35a1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -329,6 +329,9 @@
android:label="@string/survey_activity_title"
android:theme="@style/OppiaThemeWithoutActionBar"
android:windowSoftInputMode="adjustNothing" />
+
+
override fun onAttach(context: Context) {
super.onAttach(context)
(fragmentComponent as FragmentComponentImpl).inject(this)
@@ -24,6 +33,10 @@ class OnboardingFragment : InjectableFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- return onboardingFragmentPresenter.handleCreateView(inflater, container)
+ return if (enableOnboardingFlowV2.value) {
+ onboardingFragmentPresenter.handleCreateView(inflater, container)
+ } else {
+ onboardingFragmentPresenterV1.handleCreateView(inflater, container)
+ }
}
}
diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt
index c9a37d9b391..22536eead2b 100644
--- a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt
@@ -3,235 +3,38 @@ package org.oppia.android.app.onboarding
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.LinearLayout
-import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
-import androidx.viewpager2.widget.ViewPager2
import org.oppia.android.R
import org.oppia.android.app.fragment.FragmentScope
-import org.oppia.android.app.model.PolicyPage
-import org.oppia.android.app.policies.RouteToPoliciesListener
-import org.oppia.android.app.recyclerview.BindableAdapter
import org.oppia.android.app.translation.AppLanguageResourceHandler
-import org.oppia.android.databinding.OnboardingFragmentBinding
-import org.oppia.android.databinding.OnboardingSlideBinding
-import org.oppia.android.databinding.OnboardingSlideFinalBinding
-import org.oppia.android.util.parser.html.HtmlParser
-import org.oppia.android.util.parser.html.PolicyType
-import org.oppia.android.util.statusbar.StatusBarColor
+import org.oppia.android.databinding.OnboardingAppLanguageSelectionFragmentBinding
import javax.inject.Inject
/** The presenter for [OnboardingFragment]. */
@FragmentScope
class OnboardingFragmentPresenter @Inject constructor(
- private val activity: AppCompatActivity,
private val fragment: Fragment,
- private val onboardingViewModel: OnboardingViewModel,
- private val onboardingSlideFinalViewModel: OnboardingSlideFinalViewModel,
- private val resourceHandler: AppLanguageResourceHandler,
- private val htmlParserFactory: HtmlParser.Factory,
- private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory
-) : OnboardingNavigationListener, HtmlParser.PolicyOppiaTagActionListener {
- private val dotsList = ArrayList()
- private lateinit var binding: OnboardingFragmentBinding
+ private val appLanguageResourceHandler: AppLanguageResourceHandler
+) {
+ private lateinit var binding: OnboardingAppLanguageSelectionFragmentBinding
+ /** Handle creation and binding of the [OnboardingFragment] layout. */
fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View {
- binding = OnboardingFragmentBinding.inflate(
+ binding = OnboardingAppLanguageSelectionFragmentBinding.inflate(
inflater,
container,
/* attachToRoot= */ false
)
- // NB: Both the view model and lifecycle owner must be set in order to correctly bind LiveData elements to
- // data-bound view models.
- binding.let {
- it.lifecycleOwner = fragment
- it.presenter = this
- it.viewModel = onboardingViewModel
- }
- setUpViewPager()
- addDots()
- return binding.root
- }
-
- private fun setUpViewPager() {
- val onboardingViewPagerBindableAdapter = createViewPagerAdapter()
- onboardingViewPagerBindableAdapter.setData(
- listOf(
- OnboardingSlideViewModel(
- context = activity, viewPagerSlide = ViewPagerSlide.SLIDE_0, resourceHandler
- ),
- OnboardingSlideViewModel(
- context = activity, viewPagerSlide = ViewPagerSlide.SLIDE_1, resourceHandler
- ),
- OnboardingSlideViewModel(
- context = activity, viewPagerSlide = ViewPagerSlide.SLIDE_2, resourceHandler
- ),
- onboardingSlideFinalViewModel
- )
- )
- binding.onboardingSlideViewPager.adapter = onboardingViewPagerBindableAdapter
- binding.onboardingSlideViewPager.registerOnPageChangeCallback(
- object : ViewPager2.OnPageChangeCallback() {
- override fun onPageScrollStateChanged(state: Int) {
- }
- override fun onPageScrolled(
- position: Int,
- positionOffset: Float,
- positionOffsetPixels: Int
- ) {
- }
+ binding.apply {
+ lifecycleOwner = fragment
- override fun onPageSelected(position: Int) {
- if (position == TOTAL_NUMBER_OF_SLIDES - 1) {
- binding.onboardingSlideViewPager.currentItem = TOTAL_NUMBER_OF_SLIDES - 1
- onboardingViewModel.slideChanged(TOTAL_NUMBER_OF_SLIDES - 1)
- } else {
- onboardingViewModel.slideChanged(ViewPagerSlide.getSlideForPosition(position).ordinal)
- }
- selectDot(position)
- onboardingStatusBarColorUpdate(position)
- }
- })
- }
-
- private fun createViewPagerAdapter(): BindableAdapter {
- return multiTypeBuilderFactory.create { viewModel ->
- when (viewModel) {
- is OnboardingSlideViewModel -> ViewType.ONBOARDING_MIDDLE_SLIDE
- is OnboardingSlideFinalViewModel -> ViewType.ONBOARDING_FINAL_SLIDE
- else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel")
- }
- }
- .registerViewDataBinder(
- viewType = ViewType.ONBOARDING_MIDDLE_SLIDE,
- inflateDataBinding = OnboardingSlideBinding::inflate,
- setViewModel = OnboardingSlideBinding::setViewModel,
- transformViewModel = { it as OnboardingSlideViewModel }
+ onboardingLanguageTitle.text = appLanguageResourceHandler.getStringInLocaleWithWrapping(
+ R.string.onboarding_language_activity_title,
+ appLanguageResourceHandler.getStringInLocale(R.string.app_name)
)
- .registerViewDataBinder(
- viewType = ViewType.ONBOARDING_FINAL_SLIDE,
- inflateDataBinding = OnboardingSlideFinalBinding::inflate,
- setViewModel = this::bindOnboardingSlideFinal,
- transformViewModel = { it as OnboardingSlideFinalViewModel }
- )
- .build()
- }
-
- private fun bindOnboardingSlideFinal(
- binding: OnboardingSlideFinalBinding,
- model: OnboardingSlideFinalViewModel
- ) {
- binding.viewModel = model
-
- val completeString: String =
- resourceHandler.getStringInLocaleWithWrapping(
- R.string.agree_to_terms,
- resourceHandler.getStringInLocale(R.string.app_name)
- )
- binding.slideTermsOfServiceAndPrivacyPolicyLinksTextView.text = htmlParserFactory.create(
- policyOppiaTagActionListener = this,
- displayLocale = resourceHandler.getDisplayLocale()
- ).parseOppiaHtml(
- completeString,
- binding.slideTermsOfServiceAndPrivacyPolicyLinksTextView,
- supportsLinks = true,
- supportsConceptCards = false
- )
- }
-
- override fun onPolicyPageLinkClicked(policyType: PolicyType) {
- when (policyType) {
- PolicyType.PRIVACY_POLICY ->
- (activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.PRIVACY_POLICY)
- PolicyType.TERMS_OF_SERVICE ->
- (activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.TERMS_OF_SERVICE)
}
- }
- private enum class ViewType {
- ONBOARDING_MIDDLE_SLIDE,
- ONBOARDING_FINAL_SLIDE
- }
-
- private fun onboardingStatusBarColorUpdate(position: Int) {
- when (position) {
- 0 -> StatusBarColor.statusBarColorUpdate(
- R.color.component_color_onboarding_1_status_bar_color,
- activity,
- false
- )
- 1 -> StatusBarColor.statusBarColorUpdate(
- R.color.component_color_onboarding_2_status_bar_color,
- activity,
- false
- )
- 2 -> StatusBarColor.statusBarColorUpdate(
- R.color.component_color_onboarding_3_status_bar_color,
- activity,
- false
- )
- 3 -> StatusBarColor.statusBarColorUpdate(
- R.color.component_color_onboarding_4_status_bar_color,
- activity,
- false
- )
- else -> StatusBarColor.statusBarColorUpdate(
- R.color.component_color_shared_activity_status_bar_color,
- activity,
- false
- )
- }
- }
-
- override fun clickOnSkip() {
- binding.onboardingSlideViewPager.currentItem = TOTAL_NUMBER_OF_SLIDES - 1
- }
-
- override fun clickOnNext() {
- val position: Int = binding.onboardingSlideViewPager.currentItem + 1
- binding.onboardingSlideViewPager.currentItem = position
- if (position != TOTAL_NUMBER_OF_SLIDES - 1) {
- onboardingViewModel.slideChanged(ViewPagerSlide.getSlideForPosition(position).ordinal)
- } else {
- onboardingViewModel.slideChanged(TOTAL_NUMBER_OF_SLIDES - 1)
- }
- selectDot(position)
- }
-
- private fun addDots() {
- val dotsLayout = binding.slideDotsContainer
- val dotIdList = ArrayList()
- dotIdList.add(R.id.onboarding_dot_0)
- dotIdList.add(R.id.onboarding_dot_1)
- dotIdList.add(R.id.onboarding_dot_2)
- dotIdList.add(R.id.onboarding_dot_3)
- for (index in 0 until TOTAL_NUMBER_OF_SLIDES) {
- val dotView = ImageView(activity)
- dotView.id = dotIdList[index]
- dotView.setImageResource(R.drawable.onboarding_dot_active)
-
- val params = LinearLayout.LayoutParams(
- activity.resources.getDimensionPixelSize(R.dimen.dot_width_height),
- activity.resources.getDimensionPixelSize(R.dimen.dot_width_height)
- )
- params.setMargins(
- activity.resources.getDimensionPixelSize(R.dimen.dot_gap),
- 0,
- 0,
- 0
- )
- dotsLayout.addView(dotView, params)
- dotsList.add(dotView)
- }
- selectDot(0)
- }
-
- private fun selectDot(position: Int) {
- for (index in 0 until TOTAL_NUMBER_OF_SLIDES) {
- val alphaValue = if (index == position) 1.0F else 0.3F
- dotsList[index].alpha = alphaValue
- }
+ return binding.root
}
}
diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenterV1.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenterV1.kt
new file mode 100644
index 00000000000..a10847e8c79
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenterV1.kt
@@ -0,0 +1,237 @@
+package org.oppia.android.app.onboarding
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.viewpager2.widget.ViewPager2
+import org.oppia.android.R
+import org.oppia.android.app.fragment.FragmentScope
+import org.oppia.android.app.model.PolicyPage
+import org.oppia.android.app.policies.RouteToPoliciesListener
+import org.oppia.android.app.recyclerview.BindableAdapter
+import org.oppia.android.app.translation.AppLanguageResourceHandler
+import org.oppia.android.databinding.OnboardingFragmentBinding
+import org.oppia.android.databinding.OnboardingSlideBinding
+import org.oppia.android.databinding.OnboardingSlideFinalBinding
+import org.oppia.android.util.parser.html.HtmlParser
+import org.oppia.android.util.parser.html.PolicyType
+import org.oppia.android.util.statusbar.StatusBarColor
+import javax.inject.Inject
+
+/** The presenter for [OnboardingFragment]. */
+@FragmentScope
+class OnboardingFragmentPresenterV1 @Inject constructor(
+ private val activity: AppCompatActivity,
+ private val fragment: Fragment,
+ private val onboardingViewModel: OnboardingViewModel,
+ private val onboardingSlideFinalViewModel: OnboardingSlideFinalViewModel,
+ private val resourceHandler: AppLanguageResourceHandler,
+ private val htmlParserFactory: HtmlParser.Factory,
+ private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory
+) : OnboardingNavigationListener, HtmlParser.PolicyOppiaTagActionListener {
+ private val dotsList = ArrayList()
+ private lateinit var binding: OnboardingFragmentBinding
+
+ fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View {
+ binding = OnboardingFragmentBinding.inflate(
+ inflater,
+ container,
+ /* attachToRoot= */ false
+ )
+ // NB: Both the view model and lifecycle owner must be set in order to correctly bind LiveData elements to
+ // data-bound view models.
+ binding.let {
+ it.lifecycleOwner = fragment
+ it.presenter = this
+ it.viewModel = onboardingViewModel
+ }
+ setUpViewPager()
+ addDots()
+ return binding.root
+ }
+
+ private fun setUpViewPager() {
+ val onboardingViewPagerBindableAdapter = createViewPagerAdapter()
+ onboardingViewPagerBindableAdapter.setData(
+ listOf(
+ OnboardingSlideViewModel(
+ context = activity, viewPagerSlide = ViewPagerSlide.SLIDE_0, resourceHandler
+ ),
+ OnboardingSlideViewModel(
+ context = activity, viewPagerSlide = ViewPagerSlide.SLIDE_1, resourceHandler
+ ),
+ OnboardingSlideViewModel(
+ context = activity, viewPagerSlide = ViewPagerSlide.SLIDE_2, resourceHandler
+ ),
+ onboardingSlideFinalViewModel
+ )
+ )
+ binding.onboardingSlideViewPager.adapter = onboardingViewPagerBindableAdapter
+ binding.onboardingSlideViewPager.registerOnPageChangeCallback(
+ object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageScrollStateChanged(state: Int) {
+ }
+
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int
+ ) {
+ }
+
+ override fun onPageSelected(position: Int) {
+ if (position == TOTAL_NUMBER_OF_SLIDES - 1) {
+ binding.onboardingSlideViewPager.currentItem = TOTAL_NUMBER_OF_SLIDES - 1
+ onboardingViewModel.slideChanged(TOTAL_NUMBER_OF_SLIDES - 1)
+ } else {
+ onboardingViewModel.slideChanged(ViewPagerSlide.getSlideForPosition(position).ordinal)
+ }
+ selectDot(position)
+ onboardingStatusBarColorUpdate(position)
+ }
+ })
+ }
+
+ private fun createViewPagerAdapter(): BindableAdapter {
+ return multiTypeBuilderFactory.create { viewModel ->
+ when (viewModel) {
+ is OnboardingSlideViewModel -> ViewType.ONBOARDING_MIDDLE_SLIDE
+ is OnboardingSlideFinalViewModel -> ViewType.ONBOARDING_FINAL_SLIDE
+ else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel")
+ }
+ }
+ .registerViewDataBinder(
+ viewType = ViewType.ONBOARDING_MIDDLE_SLIDE,
+ inflateDataBinding = OnboardingSlideBinding::inflate,
+ setViewModel = OnboardingSlideBinding::setViewModel,
+ transformViewModel = { it as OnboardingSlideViewModel }
+ )
+ .registerViewDataBinder(
+ viewType = ViewType.ONBOARDING_FINAL_SLIDE,
+ inflateDataBinding = OnboardingSlideFinalBinding::inflate,
+ setViewModel = this::bindOnboardingSlideFinal,
+ transformViewModel = { it as OnboardingSlideFinalViewModel }
+ )
+ .build()
+ }
+
+ private fun bindOnboardingSlideFinal(
+ binding: OnboardingSlideFinalBinding,
+ model: OnboardingSlideFinalViewModel
+ ) {
+ binding.viewModel = model
+
+ val completeString: String =
+ resourceHandler.getStringInLocaleWithWrapping(
+ R.string.agree_to_terms,
+ resourceHandler.getStringInLocale(R.string.app_name)
+ )
+ binding.slideTermsOfServiceAndPrivacyPolicyLinksTextView.text = htmlParserFactory.create(
+ policyOppiaTagActionListener = this,
+ displayLocale = resourceHandler.getDisplayLocale()
+ ).parseOppiaHtml(
+ completeString,
+ binding.slideTermsOfServiceAndPrivacyPolicyLinksTextView,
+ supportsLinks = true,
+ supportsConceptCards = false
+ )
+ }
+
+ override fun onPolicyPageLinkClicked(policyType: PolicyType) {
+ when (policyType) {
+ PolicyType.PRIVACY_POLICY ->
+ (activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.PRIVACY_POLICY)
+ PolicyType.TERMS_OF_SERVICE ->
+ (activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.TERMS_OF_SERVICE)
+ }
+ }
+
+ private enum class ViewType {
+ ONBOARDING_MIDDLE_SLIDE,
+ ONBOARDING_FINAL_SLIDE
+ }
+
+ private fun onboardingStatusBarColorUpdate(position: Int) {
+ when (position) {
+ 0 -> StatusBarColor.statusBarColorUpdate(
+ R.color.component_color_onboarding_1_status_bar_color,
+ activity,
+ false
+ )
+ 1 -> StatusBarColor.statusBarColorUpdate(
+ R.color.component_color_onboarding_2_status_bar_color,
+ activity,
+ false
+ )
+ 2 -> StatusBarColor.statusBarColorUpdate(
+ R.color.component_color_onboarding_3_status_bar_color,
+ activity,
+ false
+ )
+ 3 -> StatusBarColor.statusBarColorUpdate(
+ R.color.component_color_onboarding_4_status_bar_color,
+ activity,
+ false
+ )
+ else -> StatusBarColor.statusBarColorUpdate(
+ R.color.component_color_shared_activity_status_bar_color,
+ activity,
+ false
+ )
+ }
+ }
+
+ override fun clickOnSkip() {
+ binding.onboardingSlideViewPager.currentItem = TOTAL_NUMBER_OF_SLIDES - 1
+ }
+
+ override fun clickOnNext() {
+ val position: Int = binding.onboardingSlideViewPager.currentItem + 1
+ binding.onboardingSlideViewPager.currentItem = position
+ if (position != TOTAL_NUMBER_OF_SLIDES - 1) {
+ onboardingViewModel.slideChanged(ViewPagerSlide.getSlideForPosition(position).ordinal)
+ } else {
+ onboardingViewModel.slideChanged(TOTAL_NUMBER_OF_SLIDES - 1)
+ }
+ selectDot(position)
+ }
+
+ private fun addDots() {
+ val dotsLayout = binding.slideDotsContainer
+ val dotIdList = ArrayList()
+ dotIdList.add(R.id.onboarding_dot_0)
+ dotIdList.add(R.id.onboarding_dot_1)
+ dotIdList.add(R.id.onboarding_dot_2)
+ dotIdList.add(R.id.onboarding_dot_3)
+ for (index in 0 until TOTAL_NUMBER_OF_SLIDES) {
+ val dotView = ImageView(activity)
+ dotView.id = dotIdList[index]
+ dotView.setImageResource(R.drawable.onboarding_dot_active)
+
+ val params = LinearLayout.LayoutParams(
+ activity.resources.getDimensionPixelSize(R.dimen.dot_width_height),
+ activity.resources.getDimensionPixelSize(R.dimen.dot_width_height)
+ )
+ params.setMargins(
+ activity.resources.getDimensionPixelSize(R.dimen.dot_gap),
+ 0,
+ 0,
+ 0
+ )
+ dotsLayout.addView(dotView, params)
+ dotsList.add(dotView)
+ }
+ selectDot(0)
+ }
+
+ private fun selectDot(position: Int) {
+ for (index in 0 until TOTAL_NUMBER_OF_SLIDES) {
+ val alphaValue = if (index == position) 1.0F else 0.3F
+ dotsList[index].alpha = alphaValue
+ }
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestActivity.kt
new file mode 100644
index 00000000000..9d8878c520f
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestActivity.kt
@@ -0,0 +1,26 @@
+package org.oppia.android.app.testing
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import org.oppia.android.R
+import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
+
+/** Test activity for ViewBindingAdapters. */
+class ColorBindingAdaptersTestActivity : InjectableAutoLocalizedAppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_color_binding_adapters_test)
+
+ supportFragmentManager.beginTransaction().add(
+ R.id.background,
+ ColorBindingAdaptersTestFragment()
+ ).commitNow()
+ }
+
+ companion object {
+ /** Intent to open this activity. */
+ fun createIntent(context: Context): Intent =
+ Intent(context, ColorBindingAdaptersTestActivity::class.java)
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestFragment.kt
new file mode 100644
index 00000000000..5c05770960c
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestFragment.kt
@@ -0,0 +1,24 @@
+package org.oppia.android.app.testing
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import org.oppia.android.R
+import org.oppia.android.app.fragment.InjectableFragment
+
+/** Test-only fragment for verifying behaviors of [ColorBindingAdapters]. */
+class ColorBindingAdaptersTestFragment : InjectableFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(
+ R.layout.color_binding_adapters_test_fragment,
+ container,
+ /* attachToRoot= */ false
+ )
+ }
+}
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 08726cc7f49..dc71fc0e90a 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
@@ -6,9 +6,9 @@ import dagger.Subcomponent
import org.oppia.android.app.customview.ChapterNotStartedContainerConstraintLayout
import org.oppia.android.app.customview.ContinueButtonView
import org.oppia.android.app.customview.LessonThumbnailImageView
+import org.oppia.android.app.customview.OppiaCurveBackgroundView
import org.oppia.android.app.customview.PromotedStoryCardView
import org.oppia.android.app.customview.SegmentedCircularProgressView
-import org.oppia.android.app.customview.SurveyOnboardingBackgroundView
import org.oppia.android.app.home.promotedlist.ComingSoonTopicsListView
import org.oppia.android.app.home.promotedlist.PromotedStoryListView
import org.oppia.android.app.player.state.DragDropSortInteractionView
@@ -42,7 +42,7 @@ interface ViewComponentImpl : ViewComponent {
fun inject(promotedStoryCardView: PromotedStoryCardView)
fun inject(promotedStoryListView: PromotedStoryListView)
fun inject(segmentedCircularProgressView: SegmentedCircularProgressView)
- fun inject(surveyOnboardingBackgroundView: SurveyOnboardingBackgroundView)
+ fun inject(oppiaCurveBackgroundView: OppiaCurveBackgroundView)
fun inject(surveyMultipleChoiceOptionView: SurveyMultipleChoiceOptionView)
fun inject(surveyNpsItemOptionView: SurveyNpsItemOptionView)
}
diff --git a/app/src/main/res/drawable/dropdown_background.xml b/app/src/main/res/drawable/dropdown_background.xml
new file mode 100644
index 00000000000..c6cca728b36
--- /dev/null
+++ b/app/src/main/res/drawable/dropdown_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_language_icon_black_24dp.xml b/app/src/main/res/drawable/ic_language_icon_black_24dp.xml
new file mode 100644
index 00000000000..e89dd7a2a58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_language_icon_black_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/otter.xml b/app/src/main/res/drawable/otter.xml
new file mode 100644
index 00000000000..bc0891510c1
--- /dev/null
+++ b/app/src/main/res/drawable/otter.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/onboarding_app_language_selection_fragment.xml b/app/src/main/res/layout-land/onboarding_app_language_selection_fragment.xml
new file mode 100644
index 00000000000..45941fad82e
--- /dev/null
+++ b/app/src/main/res/layout-land/onboarding_app_language_selection_fragment.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/survey_welcome_dialog_fragment.xml b/app/src/main/res/layout-land/survey_welcome_dialog_fragment.xml
index 9a41b4cf558..efa851226d6 100644
--- a/app/src/main/res/layout-land/survey_welcome_dialog_fragment.xml
+++ b/app/src/main/res/layout-land/survey_welcome_dialog_fragment.xml
@@ -22,10 +22,11 @@
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.10" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-sw600dp-port/onboarding_app_language_selection_fragment.xml b/app/src/main/res/layout-sw600dp-port/onboarding_app_language_selection_fragment.xml
new file mode 100644
index 00000000000..d631c75b742
--- /dev/null
+++ b/app/src/main/res/layout-sw600dp-port/onboarding_app_language_selection_fragment.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-w600dp/survey_welcome_dialog_fragment.xml b/app/src/main/res/layout-w600dp/survey_welcome_dialog_fragment.xml
index d140d187690..ee0f8f3fa19 100644
--- a/app/src/main/res/layout-w600dp/survey_welcome_dialog_fragment.xml
+++ b/app/src/main/res/layout-w600dp/survey_welcome_dialog_fragment.xml
@@ -22,10 +22,11 @@
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.10" />
-
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/color_binding_adapters_test_fragment.xml b/app/src/main/res/layout/color_binding_adapters_test_fragment.xml
new file mode 100644
index 00000000000..903773ef5d2
--- /dev/null
+++ b/app/src/main/res/layout/color_binding_adapters_test_fragment.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/app/src/main/res/layout/onboarding_app_language_selection_fragment.xml b/app/src/main/res/layout/onboarding_app_language_selection_fragment.xml
new file mode 100644
index 00000000000..cdb7864b405
--- /dev/null
+++ b/app/src/main/res/layout/onboarding_app_language_selection_fragment.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/survey_outro_dialog_fragment.xml b/app/src/main/res/layout/survey_outro_dialog_fragment.xml
index 0b1719efc04..0aecbfbbd91 100644
--- a/app/src/main/res/layout/survey_outro_dialog_fragment.xml
+++ b/app/src/main/res/layout/survey_outro_dialog_fragment.xml
@@ -21,10 +21,11 @@
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.15" />
-
-
@color/color_def_black_24
@color/color_def_white
@color/color_def_forest_green
- @color/color_def_oppia_green
+ @color/color_def_dark_green
@color/color_def_dark_green
@color/color_def_oppia_green
@color/color_def_highlight_blue_darker
@@ -228,4 +228,7 @@
@color/color_def_black_87
@color/color_def_white
@color/color_def_black_25
+
+ @color/color_def_dark_green
+ @color/color_def_accessible_grey
diff --git a/app/src/main/res/values/color_palette.xml b/app/src/main/res/values/color_palette.xml
index f5202bb9365..36cf4fa64ef 100644
--- a/app/src/main/res/values/color_palette.xml
+++ b/app/src/main/res/values/color_palette.xml
@@ -268,4 +268,7 @@
@color/color_def_black_87
@color/color_def_black_87
@color/color_def_black_25
+
+ @color/color_def_oppia_green
+ @color/color_def_accessible_grey
diff --git a/app/src/main/res/values/component_colors.xml b/app/src/main/res/values/component_colors.xml
index 35df91176ed..3de87773381 100644
--- a/app/src/main/res/values/component_colors.xml
+++ b/app/src/main/res/values/component_colors.xml
@@ -304,4 +304,7 @@
@color/color_palette_button_text_color
@color/color_palette_edit_text_unselected_color
@color/color_palette_button_shadow_color
+ @color/color_palette_white_text_color
+ @color/color_palette_onboarding_primary_color
+ @color/color_palette_onboarding_primary_text_color
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 65e5ca43e22..fe44bb45857 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -773,4 +773,33 @@
28dp
20dp
3dp
+
+
+ 144dp
+
+
+ 4dp
+ 8dp
+ 4dp
+ 2dp
+ 4dp
+
+ 12sp
+ 14sp
+ 16sp
+ 20sp
+ 32sp
+
+
+ 4dp
+ 8dp
+ 16dp
+ 20dp
+ 28dp
+
+ 8dp
+ 12dp
+ 16dp
+ 28dp
+ 36dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8e3f401fa64..a4978ce466f 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -635,4 +635,14 @@
Lock Icon
Download Status
html Content
+
+ Welcome to %s!
+ Learn math for free, anytime!
+ Made for students aged 7 to 14
+ Select a language to start
+ You can change your language selection anytime in the app settings
+ Let\'s go!
+
+
+ Cute otter wearing glasses.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 815ceccc4b7..4ca3883d1fe 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -636,4 +636,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel
index 96d6c339ede..91bc0418d6b 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel
+++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel
@@ -363,3 +363,39 @@ app_test(
"//utility/src/main/java/org/oppia/android/util/networking:debug_module",
],
)
+
+app_test(
+ name = "ColorBindingAdaptersTest",
+ processed_src = test_with_resources("ColorBindingAdaptersTest.kt"),
+ test_class = "org.oppia.android.app.databinding.ColorBindingAdaptersTest",
+ deps = [
+ "//:dagger",
+ "//app",
+ "//app:test_deps",
+ "//app/src/main/java/org/oppia/android/app/activity:activity_intent_factories_shim",
+ "//app/src/main/java/org/oppia/android/app/application:application_component",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector_provider",
+ "//app/src/main/java/org/oppia/android/app/application:common_application_modules",
+ "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module",
+ "//app/src/main/java/org/oppia/android/app/translation/testing:test_module",
+ "//domain",
+ "//testing",
+ "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule",
+ "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/threading:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/time:test_module",
+ "//third_party:androidx_test_ext_junit",
+ "//third_party:androidx_test_ext_truth",
+ "//third_party:junit_junit",
+ "//third_party:org_robolectric_robolectric",
+ "//third_party:robolectric_android-all",
+ "//utility/src/main/java/org/oppia/android/util/accessibility:test_module",
+ "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module",
+ "//utility/src/main/java/org/oppia/android/util/data:data_providers",
+ "//utility/src/main/java/org/oppia/android/util/logging:prod_module",
+ "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module",
+ "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module",
+ "//utility/src/main/java/org/oppia/android/util/networking:debug_module",
+ ],
+)
diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt
new file mode 100644
index 00000000000..393311789b3
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt
@@ -0,0 +1,223 @@
+package org.oppia.android.app.databinding
+
+import android.app.Application
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.intent.Intents
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.databinding.ColorBindingAdapters.setCustomBackgroundColor
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.testing.ColorBindingAdaptersTestActivity
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestImageLoaderModule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.firebase.TestAuthenticationModule
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tests for [MarginBindingAdapters]. */
+@RunWith(AndroidJUnit4::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+@Config(
+ application = ColorBindingAdaptersTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+class ColorBindingAdaptersTest {
+ @Inject lateinit var context: Context
+ @get:Rule val oppiaTestRule = OppiaTestRule()
+ @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ private val whiteColor = Color.WHITE
+ private val blackColor = Color.BLACK
+
+ @Before
+ fun setUp() {
+ setUpTestApplicationComponent()
+ Intents.init()
+ }
+
+ @After
+ fun tearDown() {
+ Intents.release()
+ }
+
+ @Test
+ fun testColorBindingAdapters_setWhiteBackground_setsColorCorrectly() {
+ launchActivity().use { scenario ->
+ scenario?.onActivity { activity ->
+ val testView: View = activity.findViewById(R.id.test_view)
+ setCustomBackgroundColor(testView, whiteColor)
+ val bg = testView.background as ColorDrawable
+ assertThat(getColorHexString(bg.color)).isEqualTo(getColorHexString(whiteColor))
+ }
+ }
+ }
+
+ @Test
+ fun testColorBindingAdapters_setBlackBackground_setsColorCorrectly() {
+ launchActivity().use { scenario ->
+ scenario?.onActivity { activity ->
+ val testView: View = activity.findViewById(R.id.test_view)
+ setCustomBackgroundColor(testView, blackColor)
+ val bg = testView.background as ColorDrawable
+ assertThat(getColorHexString(bg.color)).isEqualTo(getColorHexString(blackColor))
+ }
+ }
+ }
+
+ private fun getColorHexString(colorId: Int): String = Integer.toHexString(colorId)
+
+ private fun launchActivity():
+ ActivityScenario? {
+ val scenario = ActivityScenario.launch(
+ ColorBindingAdaptersTestActivity.createIntent(context)
+ )
+ testCoroutineDispatchers.runCurrent()
+ return scenario
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them.
+ @Singleton
+ @Component(
+ modules = [
+ RobolectricModule::class,
+ PlatformParameterModule::class, PlatformParameterSingletonModule::class,
+ TestDispatcherModule::class, ApplicationModule::class,
+ LoggerModule::class, ContinueModule::class, FractionInputModule::class,
+ ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
+ NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class,
+ DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class,
+ GcsResourceModule::class, TestImageLoaderModule::class, ImageParsingModule::class,
+ HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class,
+ AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class,
+ ExpirationMetaDataRetrieverModule::class,
+ ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class,
+ ApplicationStartupListenerModule::class, LogReportWorkerModule::class,
+ HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
+ MathEquationInputModule::class, SplitScreenInteractionModule::class,
+ LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class,
+ MetricLogSchedulerModule::class, TestingBuildFlavorModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class,
+ TestAuthenticationModule::class
+ ]
+ )
+ /** Create a TestApplicationComponent. */
+ interface TestApplicationComponent : ApplicationComponent {
+ /** Build the TestApplicationComponent. */
+ @Component.Builder
+ interface Builder : ApplicationComponent.Builder
+
+ /** Inject [ColorBindingAdaptersTest] in TestApplicationComponent . */
+ fun inject(colorBindingAdaptersTest: ColorBindingAdaptersTest)
+ }
+
+ /**
+ * Class to override a dependency throughout the test application, instead of overriding the
+ * dependencies in every test class, we can just do it once by extending the Application class.
+ */
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerColorBindingAdaptersTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build() as TestApplicationComponent
+ }
+
+ /** Inject [ColorBindingAdaptersTest] in TestApplicationComponent . */
+ fun inject(colorBindingAdaptersTest: ColorBindingAdaptersTest) {
+ component.inject(colorBindingAdaptersTest)
+ }
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt
index 5dd5ef34819..46f1b08fe88 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt
@@ -34,7 +34,6 @@ import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matcher
import org.junit.After
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -146,13 +145,6 @@ class OnboardingFragmentTest {
@field:DefaultResourceBucketName
lateinit var resourceBucketName: String
- @Before
- fun setUp() {
- Intents.init()
- setUpTestApplicationComponent()
- testCoroutineDispatchers.registerIdlingResource()
- }
-
@After
fun tearDown() {
testCoroutineDispatchers.unregisterIdlingResource()
@@ -165,6 +157,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkDefaultSlideTitle_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(
allOf(
@@ -177,6 +170,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkDefaultSlideDescription_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(
allOf(
@@ -189,6 +183,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkDefaultSlide_index0DotIsActive_otherDotsAreInactive() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(
allOf(
@@ -219,6 +214,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkDefaultSlide_skipButtonIsVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.skip_text_view)).check(matches(isDisplayed()))
}
@@ -226,6 +222,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkDefaultSlide_getStartedButtonIsNotVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.get_started_button)).check(doesNotExist())
}
@@ -233,6 +230,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_swipeRight_doesNotWork() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(swipeRight())
onView(
@@ -246,6 +244,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide1Title_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
testCoroutineDispatchers.runCurrent()
@@ -260,6 +259,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide1Description_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
testCoroutineDispatchers.runCurrent()
@@ -274,6 +274,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide1_index1DotIsActive_otherDotsAreInactive() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
onView(
@@ -305,6 +306,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide1_skipButtonIsVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
testCoroutineDispatchers.runCurrent()
@@ -314,6 +316,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide1_clickSkipButton_shiftsToLastSlide() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
testCoroutineDispatchers.runCurrent()
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
@@ -331,6 +334,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide1_getStartedButtonIsNotVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
onView(withId(R.id.get_started_button)).check(doesNotExist())
@@ -339,6 +343,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_swipeLeftThenSwipeRight_isWorking() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 0))
@@ -354,6 +359,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide2Title_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
testCoroutineDispatchers.runCurrent()
@@ -368,6 +374,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide2Description_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
testCoroutineDispatchers.runCurrent()
@@ -382,6 +389,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide2_index2DotIsActive_otherDotsAreInactive() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
onView(
@@ -413,6 +421,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide2_skipButtonIsVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
testCoroutineDispatchers.runCurrent()
@@ -422,6 +431,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide2_clickSkipButton_shiftsToLastSlide() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
testCoroutineDispatchers.runCurrent()
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
@@ -439,6 +449,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide2_getStartedButtonIsNotVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
onView(withId(R.id.get_started_button)).check(doesNotExist())
@@ -447,6 +458,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide3Title_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 3))
testCoroutineDispatchers.runCurrent()
@@ -461,6 +473,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide3Description_isCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 3))
testCoroutineDispatchers.runCurrent()
@@ -475,6 +488,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide3_skipButtonIsNotVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 3))
testCoroutineDispatchers.runCurrent()
@@ -484,6 +498,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide3_getStartedButtonIsVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 3))
testCoroutineDispatchers.runCurrent()
@@ -493,6 +508,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide3_clickGetStartedButton_opensProfileActivity() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
testCoroutineDispatchers.runCurrent()
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 3))
@@ -505,6 +521,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_swipeLeftOnLastSlide_doesNotWork() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 3))
testCoroutineDispatchers.runCurrent()
@@ -520,6 +537,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_slide0Title_changeOrientation_titleIsCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(isRoot()).perform(orientationLandscape())
onView(
@@ -533,6 +551,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_moveToSlide1_changeOrientation_titleIsCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
testCoroutineDispatchers.runCurrent()
@@ -548,6 +567,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_clickOnSkip_changeOrientation_titleIsCorrect() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
testCoroutineDispatchers.runCurrent()
onView(withId(R.id.skip_text_view)).perform(click())
@@ -564,6 +584,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_nextArrowIcon_hasCorrectContentDescription() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_fragment_next_image_view)).check(
matches(
@@ -577,6 +598,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_configChange_nextArrowIcon_hasCorrectContentDescription() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(isRoot()).perform(orientationLandscape())
onView(withId(R.id.onboarding_fragment_next_image_view)).check(
@@ -591,6 +613,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_moveToSlide1_bottomDots_hasCorrectContentDescription() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
testCoroutineDispatchers.runCurrent()
@@ -606,6 +629,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_configChange_moveToSlide1_bottomDots_hasCorrectContentDescription() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 1))
testCoroutineDispatchers.runCurrent()
@@ -622,6 +646,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_moveToSlide2_bottomDots_hasCorrectContentDescription() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
testCoroutineDispatchers.runCurrent()
@@ -637,6 +662,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_configChange_moveToSlide2_bottomDots_hasCorrectContentDescription() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
onView(withId(R.id.onboarding_slide_view_pager)).perform(scrollToPosition(position = 2))
testCoroutineDispatchers.runCurrent()
@@ -653,6 +679,7 @@ class OnboardingFragmentTest {
@Test
fun testOnboardingFragment_checkSlide3_policiesLinkIsVisible() {
+ setUpTestWithOnboardingV2Disabled()
launch(OnboardingActivity::class.java).use {
testCoroutineDispatchers.runCurrent()
onView(withId(R.id.skip_text_view)).perform(click())
@@ -668,6 +695,119 @@ class OnboardingFragmentTest {
}
}
+ @Test
+ fun testOnboardingFragment_onboardingV2Enabled_screenIsCorrectlyDisplayed() {
+ setUpTestWithOnboardingV2Enabled()
+
+ launch(OnboardingActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.onboarding_language_title)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_subtitle)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_text)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_label)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_dropdown_background)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_explanation)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_lets_go_button)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_app_language_image)).check(
+ matches(
+ withContentDescription(
+ R.string.onboarding_otter_content_description
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testOnboardingFragment_onboardingV2Enabled_configChange_screenIsCorrectlyDisplayed() {
+ setUpTestWithOnboardingV2Enabled()
+
+ launch(OnboardingActivity::class.java).use {
+ onView(isRoot()).perform(orientationLandscape())
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.onboarding_language_title)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_subtitle)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_text)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_label)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_dropdown_background)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_explanation)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_lets_go_button)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_app_language_image)).check(
+ matches(
+ withContentDescription(
+ R.string.onboarding_otter_content_description
+ )
+ )
+ )
+ }
+ }
+
+ @Config(qualifiers = "sw600dp-port")
+ @Test
+ fun testOnboardingFragment_onboardingV2Enabled_tabletPortrait_screenIsCorrectlyDisplayed() {
+ setUpTestWithOnboardingV2Enabled()
+
+ launch(OnboardingActivity::class.java).use {
+ onView(isRoot()).perform(orientationLandscape())
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.onboarding_language_title)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_subtitle)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_text)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_label)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_dropdown_background)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_explanation)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_lets_go_button)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_app_language_image)).check(
+ matches(
+ withContentDescription(
+ R.string.onboarding_otter_content_description
+ )
+ )
+ )
+ }
+ }
+
+ @Config(qualifiers = "sw600dp-land")
+ @Test
+ fun testOnboardingFragment_onboardingV2Enabled_tabletLandscape_screenIsCorrectlyDisplayed() {
+ setUpTestWithOnboardingV2Enabled()
+
+ launch(OnboardingActivity::class.java).use {
+ onView(isRoot()).perform(orientationLandscape())
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.onboarding_language_title)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_subtitle)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_text)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_label)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_dropdown_background)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_explanation)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_language_lets_go_button)).check(matches(isDisplayed()))
+ onView(withId(R.id.onboarding_app_language_image)).check(
+ matches(
+ withContentDescription(
+ R.string.onboarding_otter_content_description
+ )
+ )
+ )
+ }
+ }
+
+ private fun setUpTestWithOnboardingV2Disabled() {
+ TestPlatformParameterModule.forceEnableOnboardingFlowV2(false)
+ setUp()
+ }
+
+ private fun setUpTestWithOnboardingV2Enabled() {
+ TestPlatformParameterModule.forceEnableOnboardingFlowV2(true)
+ setUp()
+ }
+
+ private fun setUp() {
+ Intents.init()
+ setUpTestApplicationComponent()
+ testCoroutineDispatchers.registerIdlingResource()
+ }
+
private fun getResources(): Resources =
ApplicationProvider.getApplicationContext().resources
diff --git a/scripts/assets/accessibility_label_exemptions.textproto b/scripts/assets/accessibility_label_exemptions.textproto
index 2ff301e7c53..a1993f3b4dd 100644
--- a/scripts/assets/accessibility_label_exemptions.textproto
+++ b/scripts/assets/accessibility_label_exemptions.textproto
@@ -10,6 +10,7 @@ exempted_activity: "app/src/main/java/org/oppia/android/app/testing/AppCompatChe
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/AudioFragmentTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/CircularProgressIndicatorAdaptersTestActivity"
+exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/DrawableBindingAdaptersTestActivity"
diff --git a/scripts/assets/file_content_validation_checks.textproto b/scripts/assets/file_content_validation_checks.textproto
index f8d2be95573..6a32f0c51ab 100644
--- a/scripts/assets/file_content_validation_checks.textproto
+++ b/scripts/assets/file_content_validation_checks.textproto
@@ -358,8 +358,9 @@ file_content_checks {
failure_message: "Only colors from component_colors.xml may be used in drawables except vector assets."
exempted_file_patterns: "app/src/main/res/drawable.*?/(ic_|lesson_thumbnail_graphic_).+?\\.xml"
exempted_file_patterns: "app/src/main/res/drawable/full_oppia_logo.xml"
- exempted_file_patterns: "app/src/main/res/drawable/rounded_white_background_with_shadow.xml"
+ exempted_file_patterns: "app/src/main/res/drawable/otter.xml"
exempted_file_patterns: "app/src/main/res/drawable/profile_image_shadow.xml"
+ exempted_file_patterns: "app/src/main/res/drawable/rounded_white_background_with_shadow.xml"
exempted_file_patterns: "app/src/main/res/drawable/selected_region_background.xml"
exempted_file_patterns: "app/src/main/res/drawable/splash_page.xml"
exempted_file_patterns: "app/src/main/res/drawable/survey_nps_radio_selected_color.xml"
diff --git a/scripts/assets/kdoc_validity_exemptions.textproto b/scripts/assets/kdoc_validity_exemptions.textproto
index 99c88f6dd6e..ffe2c3d91e4 100644
--- a/scripts/assets/kdoc_validity_exemptions.textproto
+++ b/scripts/assets/kdoc_validity_exemptions.textproto
@@ -3,7 +3,6 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/application/Applica
exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ApplicationInjectorProvider.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ApplicationStartupListenerModule.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/administratorcontrols/RouteToProfileListListener.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/SurveyOnboardingBackgroundView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragment.kt"
@@ -79,7 +78,7 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/Updates
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboadingSlideViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingActivityPresenter.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenterV1.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingSlideFinalViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/RouteToProfileListListener.kt"
diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto
index ccc591f02a4..297e97fce8e 100644
--- a/scripts/assets/test_file_exemptions.textproto
+++ b/scripts/assets/test_file_exemptions.textproto
@@ -70,9 +70,9 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/
exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/ChapterNotStartedContainerConstraintLayout.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/OppiaCurveBackgroundView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/PromotedStoryCardView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/SegmentedCircularProgressView.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/SurveyOnboardingBackgroundView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/VerticalDashedLineView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/interaction/FractionInputInteractionView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/interaction/NumericInputInteractionView.kt"
@@ -248,6 +248,7 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/OsDe
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboadingSlideViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingActivityPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenterV1.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingNavigationListener.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingSlideFinalViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingViewModel.kt"
@@ -472,6 +473,8 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/BindableAda
exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/CircularProgressIndicatorAdaptersTestActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/CircularProgressIndicatorAdaptersTestViewModel.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestActivity.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ColorBindingAdaptersTestFragment.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivityPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt"