-
Notifications
You must be signed in to change notification settings - Fork 570
Fix part of #5571: Support practice questions #5729
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
base: develop
Are you sure you want to change the base?
Changes from 31 commits
c851395
2cc5137
dd106e7
8394a3f
ec425d5
19b6bb7
e1cf476
4c35b71
b58eb88
d7ecb4d
f610e90
59fc043
a8e10c6
c1d994e
b2796b7
af87445
8b0154d
4088c53
bae1331
80bf4b2
176ee96
45ee60f
b09e3f5
370177a
ba5a5fe
ae940ac
de66aa3
3edff87
52a131c
94c04b3
e932376
070e02e
fd4388e
6faf94a
a9b7f9b
10c1ed1
79b4de9
302f696
505167e
0e1006f
fe034ee
1c91582
13b80ba
ca3765f
f72b337
77920b5
134292c
ee9e2b9
d43197c
cabedae
4206099
b3b792a
183908c
7ef3a68
482ff9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,8 @@ import org.oppia.android.app.ui.R | |
import org.oppia.android.domain.oppialogger.OppiaLogger | ||
import org.oppia.android.domain.oppialogger.analytics.AnalyticsController | ||
import org.oppia.android.util.accessibility.AccessibilityService | ||
import org.oppia.android.util.platformparameter.EnableExtraTopicTabsUi | ||
import org.oppia.android.util.platformparameter.EnableTopicInfoTab | ||
import org.oppia.android.util.platformparameter.EnableTopicPracticeTab | ||
import org.oppia.android.util.platformparameter.PlatformParameterValue | ||
import javax.inject.Inject | ||
|
||
|
@@ -33,7 +34,8 @@ class TopicFragmentPresenter @Inject constructor( | |
private val viewModel: TopicViewModel, | ||
private val oppiaLogger: OppiaLogger, | ||
private val analyticsController: AnalyticsController, | ||
@EnableExtraTopicTabsUi private val enableExtraTopicTabsUi: PlatformParameterValue<Boolean>, | ||
@EnableTopicInfoTab private val enableTopicInfoTab: PlatformParameterValue<Boolean>, | ||
@EnableTopicPracticeTab private val enableTopicPracticeTab: PlatformParameterValue<Boolean>, | ||
private val resourceHandler: AppLanguageResourceHandler | ||
) { | ||
@Inject | ||
|
@@ -76,7 +78,10 @@ class TopicFragmentPresenter @Inject constructor( | |
viewModel.setTopicId(topicId) | ||
binding.viewModel = viewModel | ||
|
||
setUpViewPager(viewPager, classroomId, topicId, isConfigChanged) | ||
viewModel.enablePracticeTab.observe(fragment) { enablePracticeTab -> | ||
setUpViewPager(viewPager, classroomId, topicId, isConfigChanged, enablePracticeTab) | ||
} | ||
|
||
return binding.root | ||
} | ||
|
||
|
@@ -120,14 +125,15 @@ class TopicFragmentPresenter @Inject constructor( | |
} | ||
|
||
private fun computeTabPosition(tab: TopicTab): Int { | ||
return if (enableExtraTopicTabsUi.value) tab.positionWithFourTabs else tab.positionWithTwoTabs | ||
return tab.getPosition(enableTopicInfoTab.value, enableTopicPracticeTab.value) | ||
} | ||
|
||
private fun setUpViewPager( | ||
viewPager2: ViewPager2, | ||
classroomId: String, | ||
topicId: String, | ||
isConfigChanged: Boolean | ||
isConfigChanged: Boolean, | ||
enablePracticeTab: Boolean | ||
|
||
) { | ||
val adapter = | ||
ViewPagerAdapter( | ||
|
@@ -136,25 +142,36 @@ class TopicFragmentPresenter @Inject constructor( | |
classroomId, | ||
topicId, | ||
storyId, | ||
enableExtraTopicTabsUi.value | ||
enableTopicInfoTab.value, | ||
enableTopicPracticeTab.value && enablePracticeTab | ||
) | ||
viewPager2.adapter = adapter | ||
TabLayoutMediator(tabLayout, viewPager2) { tab, position -> | ||
val topicTab = TopicTab.getTabForPosition(position, enableExtraTopicTabsUi.value) | ||
val topicTab = TopicTab.getTabForPosition( | ||
position, | ||
enableTopicInfoTab.value, | ||
enableTopicPracticeTab.value && enablePracticeTab | ||
) | ||
Comment on lines
162
to
166
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like you to review this old code, and determine whether we still need the code under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took a look through the code. |
||
tab.text = resourceHandler.getStringInLocale(topicTab.tabLabelResId) | ||
tab.icon = ContextCompat.getDrawable(activity, topicTab.tabIconResId) | ||
tab.contentDescription = resourceHandler.getStringInLocale(topicTab.contentDescriptionResId) | ||
}.attach() | ||
if (!isConfigChanged && topicId.isNotEmpty()) { | ||
if (enableExtraTopicTabsUi.value) { | ||
if (enableTopicInfoTab.value) { | ||
setCurrentTab(if (storyId.isNotEmpty()) TopicTab.LEARN else TopicTab.INFO) | ||
} else { | ||
setCurrentTab(TopicTab.LEARN) | ||
} | ||
} | ||
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { | ||
override fun onPageSelected(position: Int) { | ||
logTopicEvents(TopicTab.getTabForPosition(position, enableExtraTopicTabsUi.value)) | ||
logTopicEvents( | ||
TopicTab.getTabForPosition( | ||
position, | ||
enableTopicInfoTab.value, | ||
enableTopicPracticeTab.value && enablePracticeTab | ||
) | ||
) | ||
} | ||
}) | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,58 +6,104 @@ import org.oppia.android.app.ui.R | |||||||||||||||||||||
|
||||||||||||||||||||||
/** Enum to store the tabs of [TopicFragment] and get tab by position. */ | ||||||||||||||||||||||
enum class TopicTab( | ||||||||||||||||||||||
val positionWithTwoTabs: Int, | ||||||||||||||||||||||
val positionWithFourTabs: Int, | ||||||||||||||||||||||
@StringRes val tabLabelResId: Int, | ||||||||||||||||||||||
@DrawableRes val tabIconResId: Int, | ||||||||||||||||||||||
@StringRes val contentDescriptionResId: Int | ||||||||||||||||||||||
@StringRes val contentDescriptionResId: Int, | ||||||||||||||||||||||
val positions: Map<TabConfig, Int> | ||||||||||||||||||||||
) { | ||||||||||||||||||||||
INFO( | ||||||||||||||||||||||
positionWithTwoTabs = -1, | ||||||||||||||||||||||
positionWithFourTabs = 0, | ||||||||||||||||||||||
tabLabelResId = R.string.info, | ||||||||||||||||||||||
tabIconResId = R.drawable.ic_info_icon_24dp, | ||||||||||||||||||||||
contentDescriptionResId = R.string.info_tab_content_description | ||||||||||||||||||||||
contentDescriptionResId = R.string.info_tab_content_description, | ||||||||||||||||||||||
positions = mapOf( | ||||||||||||||||||||||
TabConfig.TwoTabs to -1, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithInfo to 0, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithPractice to -1, | ||||||||||||||||||||||
TabConfig.FourTabs to 0 | ||||||||||||||||||||||
) | ||||||||||||||||||||||
), | ||||||||||||||||||||||
LEARN( | ||||||||||||||||||||||
positionWithTwoTabs = 0, | ||||||||||||||||||||||
positionWithFourTabs = 1, | ||||||||||||||||||||||
tabLabelResId = R.string.learn, | ||||||||||||||||||||||
tabIconResId = R.drawable.ic_lessons_icon_24dp, | ||||||||||||||||||||||
contentDescriptionResId = R.string.lessons_tab_content_description | ||||||||||||||||||||||
contentDescriptionResId = R.string.lessons_tab_content_description, | ||||||||||||||||||||||
positions = mapOf( | ||||||||||||||||||||||
TabConfig.TwoTabs to 0, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithInfo to 1, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithPractice to 0, | ||||||||||||||||||||||
TabConfig.FourTabs to 1 | ||||||||||||||||||||||
) | ||||||||||||||||||||||
), | ||||||||||||||||||||||
PRACTICE( | ||||||||||||||||||||||
positionWithTwoTabs = -1, | ||||||||||||||||||||||
positionWithFourTabs = 2, | ||||||||||||||||||||||
tabLabelResId = R.string.practice, | ||||||||||||||||||||||
tabIconResId = R.drawable.ic_practice_icon_24dp, | ||||||||||||||||||||||
contentDescriptionResId = R.string.practice_tab_content_description | ||||||||||||||||||||||
contentDescriptionResId = R.string.practice_tab_content_description, | ||||||||||||||||||||||
positions = mapOf( | ||||||||||||||||||||||
TabConfig.TwoTabs to -1, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithInfo to -1, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithPractice to 1, | ||||||||||||||||||||||
TabConfig.FourTabs to 2 | ||||||||||||||||||||||
) | ||||||||||||||||||||||
), | ||||||||||||||||||||||
STUDY( | ||||||||||||||||||||||
positionWithTwoTabs = 1, | ||||||||||||||||||||||
positionWithFourTabs = 3, | ||||||||||||||||||||||
tabLabelResId = R.string.study, | ||||||||||||||||||||||
tabIconResId = R.drawable.ic_revision_icon_24dp, | ||||||||||||||||||||||
contentDescriptionResId = R.string.revision_tab_content_description | ||||||||||||||||||||||
contentDescriptionResId = R.string.revision_tab_content_description, | ||||||||||||||||||||||
positions = mapOf( | ||||||||||||||||||||||
TabConfig.TwoTabs to 1, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithInfo to 2, | ||||||||||||||||||||||
TabConfig.ThreeTabsWithPractice to 2, | ||||||||||||||||||||||
TabConfig.FourTabs to 3 | ||||||||||||||||||||||
) | ||||||||||||||||||||||
); | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** Enum representing different tab configurations. */ | ||||||||||||||||||||||
enum class TabConfig { | ||||||||||||||||||||||
TwoTabs, | ||||||||||||||||||||||
ThreeTabsWithInfo, | ||||||||||||||||||||||
ThreeTabsWithPractice, | ||||||||||||||||||||||
FourTabs; | ||||||||||||||||||||||
|
||||||||||||||||||||||
companion object { | ||||||||||||||||||||||
fun getConfig(enableInfo: Boolean, enablePractice: Boolean) = when { | ||||||||||||||||||||||
enableInfo && enablePractice -> FourTabs | ||||||||||||||||||||||
enableInfo -> ThreeTabsWithInfo | ||||||||||||||||||||||
enablePractice -> ThreeTabsWithPractice | ||||||||||||||||||||||
else -> TwoTabs | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** Returns the tab position based on enabled tabs. */ | ||||||||||||||||||||||
fun getPosition(enableTopicInfoTab: Boolean, enableTopicPracticeTab: Boolean): Int { | ||||||||||||||||||||||
val config = TabConfig.getConfig(enableTopicInfoTab, enableTopicPracticeTab) | ||||||||||||||||||||||
return positions[config] ?: -1 // -1 indicates that the tab is not available | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
companion object { | ||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Returns the [TopicTab] corresponding to the specified tab position, considering whether the | ||||||||||||||||||||||
* info and practice tabs are enabled per [enableExtraTopicTabsUi]. | ||||||||||||||||||||||
* info and practice tabs are enabled per [enableTopicInfoTab] and [enableTopicPracticeTab] | ||||||||||||||||||||||
* respectively. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
fun getTabForPosition(position: Int, enableExtraTopicTabsUi: Boolean): TopicTab { | ||||||||||||||||||||||
return checkNotNull( | ||||||||||||||||||||||
values().find { | ||||||||||||||||||||||
position == if (enableExtraTopicTabsUi) { | ||||||||||||||||||||||
it.positionWithFourTabs | ||||||||||||||||||||||
} else it.positionWithTwoTabs | ||||||||||||||||||||||
} | ||||||||||||||||||||||
) { "No tab corresponding to position: $position" } | ||||||||||||||||||||||
fun getTabForPosition( | ||||||||||||||||||||||
position: Int, | ||||||||||||||||||||||
enableTopicInfoTab: Boolean, | ||||||||||||||||||||||
enableTopicPracticeTab: Boolean | ||||||||||||||||||||||
): TopicTab { | ||||||||||||||||||||||
return values().find { | ||||||||||||||||||||||
it.getPosition(enableTopicInfoTab, enableTopicPracticeTab) == position | ||||||||||||||||||||||
} ?: error("No tab corresponding to position: $position") | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** Returns the number of active tabs considering [enableExtraTopicTabsUi]. */ | ||||||||||||||||||||||
fun getTabCount(enableExtraTopicTabsUi: Boolean) = | ||||||||||||||||||||||
if (enableExtraTopicTabsUi) values().size else values().size - 2 | ||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Returns the number of active tabs considering [enableTopicInfoTab] and | ||||||||||||||||||||||
* [enableTopicPracticeTab]. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
fun getTabCount(enableTopicInfoTab: Boolean, enableTopicPracticeTab: Boolean) = | ||||||||||||||||||||||
if (enableTopicInfoTab && enableTopicPracticeTab) | ||||||||||||||||||||||
values().size | ||||||||||||||||||||||
else if (enableTopicInfoTab || enableTopicPracticeTab) | ||||||||||||||||||||||
values().size - 1 | ||||||||||||||||||||||
else values().size - 2 | ||||||||||||||||||||||
|
fun getTabCount(enableTopicInfoTab: Boolean, enableTopicPracticeTab: Boolean) = | |
if (enableTopicInfoTab && enableTopicPracticeTab) | |
values().size | |
else if (enableTopicInfoTab || enableTopicPracticeTab) | |
values().size - 1 | |
else values().size - 2 | |
fun getTabCount(enableTopicInfoTab: Boolean, enableTopicPracticeTab: Boolean): Int { | |
val config = TabConfig.getConfig(enableTopicInfoTab, enableTopicPracticeTab) | |
return values().count { it.positions[config] != -1 } | |
} |
This is less brittle/more flexible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,6 +78,12 @@ class TopicViewModel @Inject constructor( | |
} | ||
} | ||
|
||
val enablePracticeTab: LiveData<Boolean> by lazy { | ||
Transformations.map(topicLiveData) { ephemeralTopic -> | ||
ephemeralTopic.topic.enablePracticeTab | ||
} | ||
} | ||
|
||
|
||
fun setProfileId(profileId: ProfileId) { | ||
this.profileId = profileId | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -170,7 +170,8 @@ class TopicActivityTest { | |
|
||
@Test | ||
fun testTopicActivity_hasCorrectActivityLabel() { | ||
TestPlatformParameterModule.forceEnableExtraTopicTabsUi(true) | ||
TestPlatformParameterModule.forceEnableTopicInfoTab(true) | ||
TestPlatformParameterModule.forceEnableTopicPracticeTab(true) | ||
launchTopicActivity( | ||
profileId, TEST_CLASSROOM_ID_1, FRACTIONS_TOPIC_ID | ||
).use { scenario -> | ||
|
@@ -186,7 +187,8 @@ class TopicActivityTest { | |
@Test | ||
@RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. | ||
fun testTopicActivity_startPracticeSession_questionActivityStartedWithProfileId() { | ||
TestPlatformParameterModule.forceEnableExtraTopicTabsUi(true) | ||
TestPlatformParameterModule.forceEnableTopicInfoTab(true) | ||
TestPlatformParameterModule.forceEnableTopicPracticeTab(true) | ||
Comment on lines
+191
to
+192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this test work with only the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Edit: We need to enable info tab as well since we are checking for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You response is not clear with regards to my original question. Is the visibility of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are in the practice session, the status of the info tab should be irrelevant. Otherwise it indicates a problem with the implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The
Considering the above setup of the test, we need to have both the feature flags enabled. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are using separate feature flag for info tab and practice tab. I think we should remove this line |
||
launchTopicActivity(profileId, TEST_CLASSROOM_ID_1, FRACTIONS_TOPIC_ID).use { | ||
// Open the practice tab and select a skill. | ||
onView(withText("Practice")).perform(click()) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LiveData should only be observed if
enableTopicPracticeTab.value
istrue
. It's best to apply gating logic at the highest level possible to simplify testing and improve code clarity.Additionally, since the topic's support for practice questions is a static property that doesn't change at runtime, using LiveData isn't appropriate in this case. A simple
val
or a computed getter function would be more suitable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have approached this by creating a boolean global variable,
enableTopicPracticeTab
, with a default value offalse
.Then I would update it's value based on
I would then use the variable everywhere else, to avoid computing
enableTopicPracticeTab.value && enablePracticeTab
every time.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following the later suggestion, I have added a global variable for
enableTopicPracticeTab
. It is updated by observing thehasPracticeQuestions
live data from the viewmodel. As mentioned in the other comment, it needs to be a live data becausehasPracticeQuestions
is getting its value from thetopicLiveData
, which is null initially untill it gets the data from the controller. So, a simpleval
or getter will not work.