Skip to content

Commit a4854e3

Browse files
authored
feat: added PLS banner for shift dates on Course Dashboard (#211)
feat: added PLS banner for shift dates on Course Dashboard - Banner added on course dashboard if its type is RESET_DATES and course is still available. - fix failed test cases - optimise code - tab position replaced with ordinal
1 parent 10ae954 commit a4854e3

File tree

18 files changed

+395
-93
lines changed

18 files changed

+395
-93
lines changed

core/src/main/java/org/openedx/core/data/api/CourseApi.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ interface CourseApi {
7171
@POST("/api/course_experience/v1/reset_course_deadlines")
7272
suspend fun resetCourseDates(@Body courseBody: Map<String, String>): ResetCourseDates
7373

74+
@GET("/api/course_experience/v1/course_deadlines_info/{course_id}")
75+
suspend fun getDatesBannerInfo(@Path("course_id") courseId: String): CourseDatesBannerInfo
76+
7477
@GET("/api/mobile/v1/course_info/{course_id}/handouts")
7578
suspend fun getHandouts(@Path("course_id") courseId: String): HandoutsModel
7679

core/src/main/java/org/openedx/core/data/model/CourseDates.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,22 @@ data class CourseDates(
1717
@SerializedName("dates_banner_info")
1818
val datesBannerInfo: DatesBannerInfo?,
1919
@SerializedName("has_ended")
20-
val hasEnded: Boolean,
20+
val hasEnded: Boolean?,
2121
) {
2222
fun getCourseDatesResult(): CourseDatesResult {
2323
return CourseDatesResult(
2424
datesSection = getStructuredCourseDates(),
25-
courseBanner = CourseDatesBannerInfo(
26-
missedDeadlines = datesBannerInfo?.missedDeadlines ?: false,
27-
missedGatedContent = datesBannerInfo?.missedGatedContent ?: false,
28-
verifiedUpgradeLink = datesBannerInfo?.verifiedUpgradeLink ?: "",
29-
contentTypeGatingEnabled = datesBannerInfo?.contentTypeGatingEnabled ?: false,
30-
hasEnded = hasEnded,
31-
)
25+
courseBanner = getDatesBannerInfo(),
26+
)
27+
}
28+
29+
private fun getDatesBannerInfo(): CourseDatesBannerInfo {
30+
return CourseDatesBannerInfo(
31+
missedDeadlines = datesBannerInfo?.missedDeadlines ?: false,
32+
missedGatedContent = datesBannerInfo?.missedGatedContent ?: false,
33+
verifiedUpgradeLink = datesBannerInfo?.verifiedUpgradeLink ?: "",
34+
contentTypeGatingEnabled = datesBannerInfo?.contentTypeGatingEnabled ?: false,
35+
hasEnded = hasEnded ?: false,
3236
)
3337
}
3438

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.openedx.core.data.model
2+
3+
import com.google.gson.annotations.SerializedName
4+
import org.openedx.core.domain.model.CourseDatesBannerInfo
5+
6+
data class CourseDatesBannerInfo(
7+
@SerializedName("dates_banner_info")
8+
val datesBannerInfo: DatesBannerInfo?,
9+
@SerializedName("has_ended")
10+
val hasEnded: Boolean?,
11+
) {
12+
fun mapToDomain(): CourseDatesBannerInfo {
13+
return CourseDatesBannerInfo(
14+
missedDeadlines = datesBannerInfo?.missedDeadlines ?: false,
15+
missedGatedContent = datesBannerInfo?.missedGatedContent ?: false,
16+
verifiedUpgradeLink = datesBannerInfo?.verifiedUpgradeLink ?: "",
17+
contentTypeGatingEnabled = datesBannerInfo?.contentTypeGatingEnabled ?: false,
18+
hasEnded = hasEnded ?: false,
19+
)
20+
}
21+
}

core/src/main/java/org/openedx/core/domain/model/CourseDatesBannerInfo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ data class CourseDatesBannerInfo(
1212
private val missedGatedContent: Boolean,
1313
private val verifiedUpgradeLink: String,
1414
private val contentTypeGatingEnabled: Boolean,
15-
private val hasEnded: Boolean
15+
private val hasEnded: Boolean,
1616
) {
1717
val bannerType by lazy { getCourseBannerType() }
1818

@@ -25,6 +25,10 @@ data class CourseDatesBannerInfo(
2525
return selfPacedAvailable || instructorPacedAvailable
2626
}
2727

28+
fun isBannerAvailableForDashboard(): Boolean {
29+
return hasEnded.not() && bannerType == RESET_DATES
30+
}
31+
2832
private fun getCourseBannerType(): CourseBannerType = when {
2933
canUpgradeToGraded() -> UPGRADE_TO_GRADED
3034
canUpgradeToReset() -> UPGRADE_TO_RESET

core/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@
100100
<string name="core_dates_info_banner_body" tools:ignore="MissingTranslation">We built a suggested schedule to help you stay on track. But don’t worry – it’s flexible so you can learn at your own pace. If you happen to fall behind, you’ll be able to adjust the dates to keep yourself on track.</string>
101101
<string name="core_dates_upgrade_to_graded_banner_body" tools:ignore="MissingTranslation">To complete graded assignments as part of this course, you can upgrade today.</string>
102102
<string name="core_dates_upgrade_to_reset_banner_body" tools:ignore="MissingTranslation">You are auditing this course, which means that you are unable to participate in graded assignments. It looks like you missed some important deadlines based on our suggested schedule. To complete graded assignments as part of this course and shift the past due assignments into the future, you can upgrade today.</string>
103+
<string name="core_dates_shift_dates_successfully_msg" tools:ignore="MissingTranslation">Your due dates have been successfully shifted to help you stay on track.</string>
104+
<string name="core_dates_shift_dates_unsuccessful_msg" tools:ignore="MissingTranslation">Your dates could not be shifted. Please try again.</string>
105+
<string name="core_dates_view_all_dates" tools:ignore="MissingTranslation"><u>View all dates</u></string>
103106

104107
<string name="core_register">Register</string>
105108
<string name="core_sign_in">Sign in</string>

course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ class CourseRepository(
106106
suspend fun resetCourseDates(courseId: String) =
107107
api.resetCourseDates(mapOf(ApiConstants.COURSE_KEY to courseId)).mapToDomain()
108108

109+
suspend fun getDatesBannerInfo(courseId: String) =
110+
api.getDatesBannerInfo(courseId).mapToDomain()
111+
109112
suspend fun getHandouts(courseId: String) = api.getHandouts(courseId).mapToDomain()
110113

111114
suspend fun getAnnouncements(courseId: String) =

course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class CourseInteractor(
7676

7777
suspend fun resetCourseDates(courseId: String) = repository.resetCourseDates(courseId)
7878

79+
suspend fun getDatesBannerInfo(courseId: String) = repository.getDatesBannerInfo(courseId)
80+
7981
suspend fun getHandouts(courseId: String) = repository.getHandouts(courseId)
8082

8183
suspend fun getAnnouncements(courseId: String) = repository.getAnnouncements(courseId)

course/src/main/java/org/openedx/course/presentation/container/CourseContainerAdapter.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,30 @@ package org.openedx.course.presentation.container
22

33
import androidx.fragment.app.Fragment
44
import androidx.viewpager2.adapter.FragmentStateAdapter
5+
import org.openedx.course.R
56

67
class CourseContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
78

8-
private val fragments = ArrayList<Fragment>()
9+
private val fragments = HashMap<CourseContainerTab, Fragment>()
910

1011
override fun getItemCount(): Int = fragments.size
1112

12-
override fun createFragment(position: Int): Fragment = fragments[position]
13+
override fun createFragment(position: Int): Fragment {
14+
val tab = CourseContainerTab.values().find { it.ordinal == position }
15+
return fragments[tab] ?: throw IllegalStateException("Fragment not found for tab $tab")
16+
}
1317

14-
fun addFragment(fragment: Fragment) {
15-
fragments.add(fragment)
18+
fun addFragment(tab: CourseContainerTab, fragment: Fragment) {
19+
fragments[tab] = fragment
1620
}
17-
}
21+
22+
fun getFragment(tab: CourseContainerTab): Fragment? = fragments[tab]
23+
}
24+
25+
enum class CourseContainerTab(val itemId: Int, val titleResId: Int) {
26+
COURSE(itemId = R.id.course, titleResId = R.string.course_navigation_course),
27+
VIDEOS(itemId = R.id.videos, titleResId = R.string.course_navigation_video),
28+
DISCUSSION(itemId = R.id.discussions, titleResId = R.string.course_navigation_discussion),
29+
DATES(itemId = R.id.dates, titleResId = R.string.course_navigation_dates),
30+
HANDOUTS(itemId = R.id.resources, titleResId = R.string.course_navigation_handouts),
31+
}

course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import org.openedx.core.presentation.global.viewBinding
1515
import org.openedx.course.R
1616
import org.openedx.course.databinding.FragmentCourseContainerBinding
1717
import org.openedx.course.presentation.CourseRouter
18+
import org.openedx.course.presentation.container.CourseContainerTab
1819
import org.openedx.course.presentation.dates.CourseDatesFragment
1920
import org.openedx.course.presentation.handouts.HandoutsFragment
2021
import org.openedx.course.presentation.outline.CourseOutlineFragment
2122
import org.openedx.course.presentation.ui.CourseToolbar
2223
import org.openedx.course.presentation.videos.CourseVideosFragment
2324
import org.openedx.discussion.presentation.topics.DiscussionTopicsFragment
25+
import org.openedx.course.presentation.container.CourseContainerTab as Tabs
2426

2527
class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
2628

@@ -93,57 +95,45 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
9395
binding.viewPager.isVisible = true
9496
binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
9597
adapter = CourseContainerAdapter(this).apply {
96-
addFragment(CourseOutlineFragment.newInstance(viewModel.courseId, viewModel.courseName))
97-
addFragment(CourseVideosFragment.newInstance(viewModel.courseId, viewModel.courseName))
98-
addFragment(DiscussionTopicsFragment.newInstance(viewModel.courseId, viewModel.courseName))
99-
addFragment(CourseDatesFragment.newInstance(viewModel.courseId, viewModel.isSelfPaced))
100-
addFragment(HandoutsFragment.newInstance(viewModel.courseId))
98+
addFragment(
99+
Tabs.COURSE,
100+
CourseOutlineFragment.newInstance(viewModel.courseId, viewModel.courseName)
101+
)
102+
addFragment(
103+
Tabs.VIDEOS,
104+
CourseVideosFragment.newInstance(viewModel.courseId, viewModel.courseName)
105+
)
106+
addFragment(
107+
Tabs.DISCUSSION,
108+
DiscussionTopicsFragment.newInstance(viewModel.courseId, viewModel.courseName)
109+
)
110+
addFragment(
111+
Tabs.DATES,
112+
CourseDatesFragment.newInstance(viewModel.courseId, viewModel.isSelfPaced)
113+
)
114+
addFragment(
115+
Tabs.HANDOUTS,
116+
HandoutsFragment.newInstance(viewModel.courseId)
117+
)
101118
}
102119
binding.viewPager.offscreenPageLimit = adapter?.itemCount ?: 1
103120
binding.viewPager.adapter = adapter
104121

105122
if (viewModel.isCourseTopTabBarEnabled) {
106123
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
107124
tab.text = getString(
108-
when (position) {
109-
0 -> R.string.course_navigation_course
110-
1 -> R.string.course_navigation_video
111-
2 -> R.string.course_navigation_discussion
112-
3 -> R.string.course_navigation_dates
113-
else -> R.string.course_navigation_handouts
114-
}
125+
Tabs.values().find { it.ordinal == position }?.titleResId
126+
?: R.string.course_navigation_course
115127
)
116128
}.attach()
117129
binding.tabLayout.isVisible = true
118130

119131
} else {
120132
binding.viewPager.isUserInputEnabled = false
121-
binding.bottomNavView.setOnItemSelectedListener {
122-
when (it.itemId) {
123-
R.id.outline -> {
124-
viewModel.courseTabClickedEvent()
125-
binding.viewPager.setCurrentItem(0, false)
126-
}
127-
128-
R.id.videos -> {
129-
viewModel.videoTabClickedEvent()
130-
binding.viewPager.setCurrentItem(1, false)
131-
}
132-
133-
R.id.discussions -> {
134-
viewModel.discussionTabClickedEvent()
135-
binding.viewPager.setCurrentItem(2, false)
136-
}
137-
138-
R.id.dates -> {
139-
viewModel.datesTabClickedEvent()
140-
binding.viewPager.setCurrentItem(3, false)
141-
}
142-
143-
R.id.resources -> {
144-
viewModel.handoutsTabClickedEvent()
145-
binding.viewPager.setCurrentItem(4, false)
146-
}
133+
binding.bottomNavView.setOnItemSelectedListener { menuItem ->
134+
Tabs.values().find { menuItem.itemId == it.itemId }?.let { tab ->
135+
viewModel.courseContainerTabClickedEvent(tab)
136+
binding.viewPager.setCurrentItem(tab.ordinal, false)
147137
}
148138
true
149139
}
@@ -155,6 +145,18 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
155145
viewModel.updateData(withSwipeRefresh)
156146
}
157147

148+
fun updateCourseDates() {
149+
adapter?.getFragment(Tabs.DATES)?.let {
150+
(it as CourseDatesFragment).updateData()
151+
}
152+
}
153+
154+
fun navigateToTab(tab: CourseContainerTab) {
155+
adapter?.getFragment(tab)?.let {
156+
binding.viewPager.setCurrentItem(tab.ordinal, true)
157+
}
158+
}
159+
158160
companion object {
159161
private const val ARG_COURSE_ID = "courseId"
160162
private const val ARG_TITLE = "title"

course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,23 +109,33 @@ class CourseContainerViewModel(
109109
}
110110
}
111111

112-
fun courseTabClickedEvent() {
112+
fun courseContainerTabClickedEvent(tab: CourseContainerTab) {
113+
when (tab) {
114+
CourseContainerTab.COURSE -> courseTabClickedEvent()
115+
CourseContainerTab.VIDEOS -> videoTabClickedEvent()
116+
CourseContainerTab.DISCUSSION -> discussionTabClickedEvent()
117+
CourseContainerTab.DATES -> datesTabClickedEvent()
118+
CourseContainerTab.HANDOUTS -> handoutsTabClickedEvent()
119+
}
120+
}
121+
122+
private fun courseTabClickedEvent() {
113123
analytics.courseTabClickedEvent(courseId, courseName)
114124
}
115125

116-
fun videoTabClickedEvent() {
126+
private fun videoTabClickedEvent() {
117127
analytics.videoTabClickedEvent(courseId, courseName)
118128
}
119129

120-
fun discussionTabClickedEvent() {
130+
private fun discussionTabClickedEvent() {
121131
analytics.discussionTabClickedEvent(courseId, courseName)
122132
}
123133

124-
fun datesTabClickedEvent() {
134+
private fun datesTabClickedEvent() {
125135
analytics.datesTabClickedEvent(courseId, courseName)
126136
}
127137

128-
fun handoutsTabClickedEvent() {
138+
private fun handoutsTabClickedEvent() {
129139
analytics.handoutsTabClickedEvent(courseId, courseName)
130140
}
131141
}

0 commit comments

Comments
 (0)