Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix #545: Low fi onboarding part 1 #558

Merged
merged 38 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b7150e8
Initial file introduction
Dec 12, 2019
7bc3b49
ViewModel amnd ViewPager introduction
Dec 12, 2019
2f98f99
Basic implementation finished
Dec 12, 2019
4c6d02e
Added images
Dec 13, 2019
c60d69c
Resolve merge conflicts
Dec 16, 2019
55f129a
New implementation
Dec 16, 2019
827a0a2
Button click listener added
Dec 16, 2019
42067d3
Skip button functionality added
Dec 16, 2019
985c531
Final implememntation
Dec 16, 2019
1bb3dc7
Added test cases
Dec 16, 2019
a345db9
Self review nit changes
Dec 16, 2019
439af56
Updated test cases- Not perfect
Dec 17, 2019
db52f8e
Indicator dots implementation change and test cases updated
Dec 18, 2019
0b07b22
Merge remote-tracking branch 'upstream/develop' into low-fi-onboardin…
Dec 19, 2019
c84679b
Added TODO
Dec 19, 2019
cbbf30b
Nit change
Dec 19, 2019
0a7bf10
Merge remote-tracking branch 'upstream/develop' into low-fi-onboardin…
Dec 24, 2019
219da2d
Resolve merge conflicts
Dec 24, 2019
d9116e6
Changes as per Ben's suggestion
Dec 24, 2019
2419ed9
Nit changes to OnboardingSlideViewModel
Dec 24, 2019
33bea19
Updated test cases
Jan 8, 2020
eb41f5b
Added contentDescription
Jan 9, 2020
4f7861e
Updates image test cases
Jan 9, 2020
fc8be33
All test cases fixed
Jan 10, 2020
fc6033e
Nit change
Jan 10, 2020
50eb1f4
Merge branch 'develop' of https://github.com/oppia/oppia-android into…
nikitamarysolomanpvt Jan 21, 2020
7f5910c
nit
nikitamarysolomanpvt Jan 22, 2020
eb85c75
nit
nikitamarysolomanpvt Jan 22, 2020
161c216
change in package structure, and changed on-board to single word onbo…
nikitamarysolomanpvt Feb 11, 2020
1ba63ea
changed on-boarded to single word onboarded
nikitamarysolomanpvt Feb 11, 2020
58c3f39
changed on-board to single word onboard in text class method names
nikitamarysolomanpvt Feb 11, 2020
e4b7854
changed on-board to single word onboard in comments
nikitamarysolomanpvt Feb 11, 2020
897b00e
nit
nikitamarysolomanpvt Feb 12, 2020
b4db282
Resolve merge conflicts with develop
Feb 14, 2020
46cea68
Merge remote-tracking branch 'upstream/domain-onboarding-flag-part-1'…
Feb 14, 2020
193840e
Onboarding flow full lowfi implementation
Feb 14, 2020
8bfb842
Updated code
Feb 18, 2020
5d70d01
Updated test cases
Feb 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
android:name=".mydownloads.MyDownloadsActivity"
android:screenOrientation="portrait"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity
android:name=".onboarding.OnboardingActivity"
android:screenOrientation="portrait"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity
android:name=".player.exploration.ExplorationActivity"
android:screenOrientation="portrait"
Expand Down Expand Up @@ -84,8 +88,10 @@
android:name=".story.testing.StoryFragmentTestActivity"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity android:name=".testing.BindableAdapterTestActivity" />
<activity android:name=".testing.ConceptCardFragmentTestActivity" />
<activity android:name=".testing.ContentCardTestActivity" />
<activity android:name=".testing.ContinuePlayingFragmentTestActivity" />
<activity android:name=".testing.ExplorationInjectionActivity" />
<activity android:name=".testing.ExplorationTestActivity" />
<activity android:name=".testing.HtmlParserTestActivity" />
<activity android:name=".testing.InputInteractionViewTestActivity" />
Expand All @@ -99,8 +105,6 @@
<activity
android:name=".testing.TopicTestActivityForStory"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity android:name=".testing.ExplorationInjectionActivity" />
<activity android:name=".testing.ConceptCardFragmentTestActivity" />
<activity
android:name=".topic.questionplayer.QuestionPlayerActivity"
android:screenOrientation="portrait" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.oppia.app.fragment.FragmentComponent
import org.oppia.app.help.HelpActivity
import org.oppia.app.home.HomeActivity
import org.oppia.app.home.continueplaying.ContinuePlayingActivity
import org.oppia.app.onboarding.OnboardingActivity
import org.oppia.app.mydownloads.MyDownloadsActivity
import org.oppia.app.player.exploration.ExplorationActivity
import org.oppia.app.profile.AddProfileActivity
Expand All @@ -18,6 +19,7 @@ import org.oppia.app.settings.profile.ProfileEditActivity
import org.oppia.app.settings.profile.ProfileListActivity
import org.oppia.app.settings.profile.ProfileRenameActivity
import org.oppia.app.settings.profile.ProfileResetPinActivity
import org.oppia.app.splash.SplashActivity
import org.oppia.app.story.StoryActivity
import org.oppia.app.story.testing.StoryFragmentTestActivity
import org.oppia.app.testing.AudioFragmentTestActivity
Expand Down Expand Up @@ -66,13 +68,15 @@ interface ActivityComponent {
fun inject(htmlParserTestActivity: HtmlParserTestActivity)
fun inject(myDownloadsActivity: MyDownloadsActivity)
fun inject(navigationDrawerTestActivity: NavigationDrawerTestActivity)
fun inject(onboardingActivity: OnboardingActivity)
fun inject(pinPasswordActivity: PinPasswordActivity)
fun inject(profileActivity: ProfileActivity)
fun inject(questionPlayerActivity: QuestionPlayerActivity)
fun inject(profileEditActivity: ProfileEditActivity)
fun inject(profileListActivity: ProfileListActivity)
fun inject(profileRenameActivity: ProfileRenameActivity)
fun inject(profileResetPinActivity: ProfileResetPinActivity)
fun inject(splashActivity: SplashActivity)
fun inject(storyActivity: StoryActivity)
fun inject(topicActivity: TopicActivity)
fun inject(topicTestActivity: TopicTestActivity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.oppia.app.drawer.NavigationDrawerFragment
import org.oppia.app.help.HelpFragment
import org.oppia.app.home.HomeFragment
import org.oppia.app.home.continueplaying.ContinuePlayingFragment
import org.oppia.app.onboarding.OnboardingFragment
import org.oppia.app.mydownloads.DownloadsTabFragment
import org.oppia.app.mydownloads.MyDownloadsFragment
import org.oppia.app.mydownloads.UpdatesTabFragment
Expand Down Expand Up @@ -54,6 +55,7 @@ interface FragmentComponent {
fun inject(homeFragment: HomeFragment)
fun inject(myDownloadsFragment: MyDownloadsFragment)
fun inject(navigationDrawerFragment: NavigationDrawerFragment)
fun inject(onboardingFragment: OnboardingFragment)
fun inject(profileChooserFragment: ProfileChooserFragment)
fun inject(questionPlayerFragment: QuestionPlayerFragment)
fun inject(resetPinDialogFragment: ResetPinDialogFragment)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.oppia.app.onboarding

import android.content.Context
import androidx.databinding.ObservableField
import androidx.lifecycle.ViewModel
import org.oppia.app.R
import org.oppia.app.viewmodel.ObservableViewModel

const val TOTAL_NUMBER_OF_SLIDES = 4

/** [ViewModel] for slide in onboarding flow. */
class OnboardingSlideViewModel(val context: Context, viewPagerSlide: ViewPagerSlide) : ObservableViewModel() {
val slideImage = ObservableField<Int>(R.drawable.ic_onboarding_0)
val contentDescription = ObservableField<String>(context.resources.getString(R.string.onboarding_slide_0_title))
val title = ObservableField<String>(context.resources.getString(R.string.onboarding_slide_0_title))
val description = ObservableField<String>(context.resources.getString(R.string.onboarding_slide_0_description))

init {
initializingSlide(viewPagerSlide)
}

private fun initializingSlide(viewPagerSlide: ViewPagerSlide) {
when (viewPagerSlide) {
ViewPagerSlide.SLIDE_0 -> {
slideImage.set(R.drawable.ic_onboarding_0)
contentDescription.set(context.resources.getString(R.string.onboarding_slide_0_title))
title.set(context.resources.getString(R.string.onboarding_slide_0_title))
description.set(context.resources.getString(R.string.onboarding_slide_0_description))
}
ViewPagerSlide.SLIDE_1 -> {
slideImage.set(R.drawable.ic_onboarding_1)
contentDescription.set(context.resources.getString(R.string.onboarding_slide_1_title))
title.set(context.resources.getString(R.string.onboarding_slide_1_title))
description.set(context.resources.getString(R.string.onboarding_slide_1_description))
}
ViewPagerSlide.SLIDE_2 -> {
slideImage.set(R.drawable.ic_onboarding_2)
contentDescription.set(context.resources.getString(R.string.onboarding_slide_2_title))
title.set(context.resources.getString(R.string.onboarding_slide_2_title))
description.set(context.resources.getString(R.string.onboarding_slide_2_description))
}
ViewPagerSlide.SLIDE_3 -> {
slideImage.set(R.drawable.ic_onboarding_3)
contentDescription.set(context.resources.getString(R.string.onboarding_slide_3_title))
title.set(context.resources.getString(R.string.onboarding_slide_3_title))
description.set(context.resources.getString(R.string.onboarding_slide_3_description))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.oppia.app.onboarding

import androidx.databinding.ObservableField
import androidx.lifecycle.ViewModel
import org.oppia.app.viewmodel.ObservableViewModel
import javax.inject.Inject

/** [ViewModel] for [OnboardingFragment]. */
class OnboardingViewModel @Inject constructor() : ObservableViewModel() {
val slideNumber = ObservableField<Int>(0)
val totalNumberOfSlides = TOTAL_NUMBER_OF_SLIDES

fun slideChanged(viewPagerSlide: ViewPagerSlide) {
slideNumber.set(viewPagerSlide.ordinal)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.oppia.app.onboarding

import android.content.Context
import android.content.Intent
import android.os.Bundle
import org.oppia.app.activity.InjectableAppCompatActivity
import org.oppia.app.profile.ProfileActivity
import javax.inject.Inject

/** Activity that contains the onboarding flow for learners. */
class OnboardingActivity : InjectableAppCompatActivity(), RouteToProfileListListener {
@Inject lateinit var onboardingActivityPresenter: OnboardingActivityPresenter

companion object {
fun createOnboardingActivity(context: Context): Intent {
val intent = Intent(context, OnboardingActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
return intent
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent.inject(this)
onboardingActivityPresenter.handleOnCreate()
}

override fun routeToProfileList() {
startActivity(ProfileActivity.createProfileActivity(this))
finish()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.oppia.app.onboarding

import androidx.appcompat.app.AppCompatActivity
import org.oppia.app.R
import org.oppia.app.activity.ActivityScope
import javax.inject.Inject

/** The presenter for [OnboardingActivity]. */
@ActivityScope
class OnboardingActivityPresenter @Inject constructor(private val activity: AppCompatActivity) {
fun handleOnCreate() {
activity.setContentView(R.layout.onboarding_activity)
if (getOnboardingFragment() == null) {
activity.supportFragmentManager.beginTransaction().add(
R.id.onboarding_fragment_placeholder,
OnboardingFragment()
).commitNow()
}
}

private fun getOnboardingFragment(): OnboardingFragment? {
return activity.supportFragmentManager.findFragmentById(R.id.onboarding_fragment_placeholder) as OnboardingFragment?
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.oppia.app.onboarding

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import org.oppia.app.fragment.InjectableFragment
import javax.inject.Inject

/** Fragment that contains an onboarding flow of the app. */
class OnboardingFragment : InjectableFragment() {
@Inject lateinit var onboardingFragmentPresenter: OnboardingFragmentPresenter

override fun onAttach(context: Context) {
super.onAttach(context)
fragmentComponent.inject(this)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return onboardingFragmentPresenter.handleCreateView(inflater, container)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.oppia.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.viewpager.widget.ViewPager
import org.oppia.app.R
import org.oppia.app.databinding.OnboardingFragmentBinding
import org.oppia.app.fragment.FragmentScope
import org.oppia.app.viewmodel.ViewModelProvider
import org.oppia.domain.onboarding.OnboardingFlowController
import javax.inject.Inject

/** The presenter for [OnboardingFragment]. */
@FragmentScope
class OnboardingFragmentPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val fragment: Fragment,
private val onboardingFlowController: OnboardingFlowController,
private val viewModelProvider: ViewModelProvider<OnboardingViewModel>
) {
private val dotsList = ArrayList<ImageView>()
private lateinit var onboardingPagerAdapter: OnboardingPagerAdapter
private val routeToProfileListener = activity as RouteToProfileListListener
private lateinit var binding: OnboardingFragmentBinding

fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? {
rt4914 marked this conversation as resolved.
Show resolved Hide resolved
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 = getOnboardingViewModel()
}
setUpViewPager()
addDots()
return binding.root
}

private fun setUpViewPager() {
onboardingPagerAdapter = OnboardingPagerAdapter(fragment.requireContext())
binding.onboardingSlideViewPager.adapter = onboardingPagerAdapter
binding.onboardingSlideViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
}

override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}

override fun onPageSelected(position: Int) {
getOnboardingViewModel().slideChanged(ViewPagerSlide.getSlideForPosition(position))
selectDot(position)
}
})
}

fun clickOnGetStarted() {
onboardingFlowController.markOnboardingFlowCompleted()
routeToProfileListener.routeToProfileList()
}

fun clickOnSkip() {
getOnboardingViewModel().slideChanged(ViewPagerSlide.SLIDE_3)
binding.onboardingSlideViewPager.currentItem = ViewPagerSlide.SLIDE_3.ordinal
}

private fun getOnboardingViewModel(): OnboardingViewModel {
return viewModelProvider.getForFragment(fragment, OnboardingViewModel::class.java)
}

private fun addDots() {
val dotsLayout = binding.slideDotsContainer
val dotIdList = ArrayList<Int>()
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,
activity.resources.getDimensionPixelSize(R.dimen.dot_gap),
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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.oppia.app.onboarding

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.viewpager.widget.PagerAdapter
import org.oppia.app.databinding.OnboardingSlideBinding

/** Adapter to control the slide details in onboarding flow. */
class OnboardingPagerAdapter(val context: Context) : PagerAdapter() {
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val binding = OnboardingSlideBinding.inflate(LayoutInflater.from(context), container, false)
val onboardingSlideViewModel = OnboardingSlideViewModel(context, ViewPagerSlide.getSlideForPosition(position))
binding.viewModel = onboardingSlideViewModel
container.addView(binding.root)
return binding.root
}

override fun getCount(): Int {
return TOTAL_NUMBER_OF_SLIDES
}

override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
rt4914 marked this conversation as resolved.
Show resolved Hide resolved
}

override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as ConstraintLayout)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.oppia.app.onboarding

/** Listener for when an activity should route to [ProfileActivity]. */
interface RouteToProfileListListener {
fun routeToProfileList()
}
18 changes: 18 additions & 0 deletions app/src/main/java/org/oppia/app/onboarding/ViewPagerSlide.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.oppia.app.onboarding

/** Enum to store the slides of [OnboardingFragment] and get slide by position. */
enum class ViewPagerSlide(private var position: Int) {
SLIDE_0(position = 0),
SLIDE_1(position = 1),
SLIDE_2(position = 2),
SLIDE_3(position = 3);

companion object {
fun getSlideForPosition(position: Int): ViewPagerSlide {
val ordinal = checkNotNull(values().map(ViewPagerSlide::position)[position]) {
"No tab corresponding to position: $position"
}
return values()[ordinal]
}
}
}
Loading