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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +