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

feat: added PLS banner for shift dates on Course Dashboard #211

Merged
merged 8 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions core/src/main/java/org/openedx/core/data/api/CourseApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ interface CourseApi {
@POST("/api/course_experience/v1/reset_course_deadlines")
suspend fun resetCourseDates(@Body courseBody: Map<String, String>): ResetCourseDates

@GET("/api/course_experience/v1/course_deadlines_info/{course_id}")
suspend fun getDatesBannerInfo(@Path("course_id") courseId: String): CourseDatesBannerInfo

@GET("/api/mobile/v1/course_info/{course_id}/handouts")
suspend fun getHandouts(@Path("course_id") courseId: String): HandoutsModel

Expand Down
20 changes: 12 additions & 8 deletions core/src/main/java/org/openedx/core/data/model/CourseDates.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@ data class CourseDates(
@SerializedName("dates_banner_info")
val datesBannerInfo: DatesBannerInfo?,
@SerializedName("has_ended")
val hasEnded: Boolean,
val hasEnded: Boolean?,
) {
fun getCourseDatesResult(): CourseDatesResult {
return CourseDatesResult(
datesSection = getStructuredCourseDates(),
courseBanner = CourseDatesBannerInfo(
missedDeadlines = datesBannerInfo?.missedDeadlines ?: false,
missedGatedContent = datesBannerInfo?.missedGatedContent ?: false,
verifiedUpgradeLink = datesBannerInfo?.verifiedUpgradeLink ?: "",
contentTypeGatingEnabled = datesBannerInfo?.contentTypeGatingEnabled ?: false,
hasEnded = hasEnded,
)
courseBanner = getDatesBannerInfo(),
)
}

private fun getDatesBannerInfo(): CourseDatesBannerInfo {
return CourseDatesBannerInfo(
missedDeadlines = datesBannerInfo?.missedDeadlines ?: false,
missedGatedContent = datesBannerInfo?.missedGatedContent ?: false,
verifiedUpgradeLink = datesBannerInfo?.verifiedUpgradeLink ?: "",
contentTypeGatingEnabled = datesBannerInfo?.contentTypeGatingEnabled ?: false,
hasEnded = hasEnded ?: false,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.core.domain.model.CourseDatesBannerInfo

data class CourseDatesBannerInfo(
@SerializedName("dates_banner_info")
val datesBannerInfo: DatesBannerInfo?,
@SerializedName("has_ended")
val hasEnded: Boolean?,
) {
fun mapToDomain(): CourseDatesBannerInfo {
return CourseDatesBannerInfo(
missedDeadlines = datesBannerInfo?.missedDeadlines ?: false,
missedGatedContent = datesBannerInfo?.missedGatedContent ?: false,
verifiedUpgradeLink = datesBannerInfo?.verifiedUpgradeLink ?: "",
contentTypeGatingEnabled = datesBannerInfo?.contentTypeGatingEnabled ?: false,
hasEnded = hasEnded ?: false,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class CourseDatesBannerInfo(
private val missedGatedContent: Boolean,
private val verifiedUpgradeLink: String,
private val contentTypeGatingEnabled: Boolean,
private val hasEnded: Boolean
private val hasEnded: Boolean,
) {
val bannerType by lazy { getCourseBannerType() }

Expand All @@ -25,6 +25,10 @@ data class CourseDatesBannerInfo(
return selfPacedAvailable || instructorPacedAvailable
}

fun isBannerAvailableForDashboard(): Boolean {
return hasEnded.not() && bannerType == RESET_DATES
}

private fun getCourseBannerType(): CourseBannerType = when {
canUpgradeToGraded() -> UPGRADE_TO_GRADED
canUpgradeToReset() -> UPGRADE_TO_RESET
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
<string name="core_continue" tools:ignore="MissingTranslation">Continue</string>
<string name="core_leaving_the_app" tools:ignore="MissingTranslation">Leaving the app</string>
<string name="core_leaving_the_app_message" tools:ignore="MissingTranslation">You are now leaving the %s app and opening a browser.</string>
<string name="course_enrollment_error" tools:ignore="MissingTranslation">Enrollment Error</string>
<string name="course_enrollment_error_message" tools:ignore="MissingTranslation">We are unable to enroll you in this course at this time using the %s mobile application. Please try again on your web browser.</string>
<string name="core_enrollment_error" tools:ignore="MissingTranslation">Enrollment Error</string>
<string name="core_enrollment_error_message" tools:ignore="MissingTranslation">We are unable to enroll you in this course at this time using the %s mobile application. Please try again on your web browser.</string>


<!-- region: Course Date Sections -->
Expand All @@ -100,6 +100,9 @@
<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>
<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>
<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>
<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>
<string name="core_dates_shift_dates_unsuccessful_msg" tools:ignore="MissingTranslation">Your dates could not be shifted. Please try again.</string>
<string name="core_dates_view_all_dates" tools:ignore="MissingTranslation"><u>View all dates</u></string>

<string name="core_register">Register</string>
<string name="core_sign_in">Sign in</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ class CourseRepository(
suspend fun resetCourseDates(courseId: String) =
api.resetCourseDates(mapOf(ApiConstants.COURSE_KEY to courseId)).mapToDomain()

suspend fun getDatesBannerInfo(courseId: String) =
api.getDatesBannerInfo(courseId).mapToDomain()

suspend fun getHandouts(courseId: String) = api.getHandouts(courseId).mapToDomain()

suspend fun getAnnouncements(courseId: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class CourseInteractor(

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

suspend fun getDatesBannerInfo(courseId: String) = repository.getDatesBannerInfo(courseId)

suspend fun getHandouts(courseId: String) = repository.getHandouts(courseId)

suspend fun getAnnouncements(courseId: String) = repository.getAnnouncements(courseId)
Expand Down
k1rill marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@ package org.openedx.course.presentation.container

import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import org.openedx.course.R

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

private val fragments = ArrayList<Fragment>()
private val fragments = HashMap<CourseContainerTab, Fragment>()

override fun getItemCount(): Int = fragments.size

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

fun addFragment(fragment: Fragment) {
fragments.add(fragment)
fun addFragment(tab: CourseContainerTab, fragment: Fragment) {
fragments[tab] = fragment
}
}

fun getFragment(tab: CourseContainerTab): Fragment? = fragments[tab]
}

enum class CourseContainerTab(val itemId: Int, val titleResId: Int) {
COURSE(itemId = R.id.course, titleResId = R.string.course_navigation_course),
VIDEOS(itemId = R.id.videos, titleResId = R.string.course_navigation_video),
DISCUSSION(itemId = R.id.discussions, titleResId = R.string.course_navigation_discussion),
DATES(itemId = R.id.dates, titleResId = R.string.course_navigation_dates),
HANDOUTS(itemId = R.id.resources, titleResId = R.string.course_navigation_handouts),
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import org.openedx.core.presentation.global.viewBinding
import org.openedx.course.R
import org.openedx.course.databinding.FragmentCourseContainerBinding
import org.openedx.course.presentation.CourseRouter
import org.openedx.course.presentation.container.CourseContainerTab
import org.openedx.course.presentation.dates.CourseDatesFragment
import org.openedx.course.presentation.handouts.HandoutsFragment
import org.openedx.course.presentation.outline.CourseOutlineFragment
import org.openedx.course.presentation.ui.CourseToolbar
import org.openedx.course.presentation.videos.CourseVideosFragment
import org.openedx.discussion.presentation.topics.DiscussionTopicsFragment
import org.openedx.course.presentation.container.CourseContainerTab as Tabs

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

Expand Down Expand Up @@ -93,57 +95,45 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
binding.viewPager.isVisible = true
binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
adapter = CourseContainerAdapter(this).apply {
addFragment(CourseOutlineFragment.newInstance(viewModel.courseId, viewModel.courseName))
addFragment(CourseVideosFragment.newInstance(viewModel.courseId, viewModel.courseName))
addFragment(DiscussionTopicsFragment.newInstance(viewModel.courseId, viewModel.courseName))
addFragment(CourseDatesFragment.newInstance(viewModel.courseId, viewModel.isSelfPaced))
addFragment(HandoutsFragment.newInstance(viewModel.courseId))
addFragment(
Tabs.COURSE,
CourseOutlineFragment.newInstance(viewModel.courseId, viewModel.courseName)
)
addFragment(
Tabs.VIDEOS,
CourseVideosFragment.newInstance(viewModel.courseId, viewModel.courseName)
)
addFragment(
Tabs.DISCUSSION,
DiscussionTopicsFragment.newInstance(viewModel.courseId, viewModel.courseName)
)
addFragment(
Tabs.DATES,
CourseDatesFragment.newInstance(viewModel.courseId, viewModel.isSelfPaced)
)
addFragment(
Tabs.HANDOUTS,
HandoutsFragment.newInstance(viewModel.courseId)
)
}
binding.viewPager.offscreenPageLimit = adapter?.itemCount ?: 1
binding.viewPager.adapter = adapter

if (viewModel.isCourseTopTabBarEnabled) {
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = getString(
when (position) {
0 -> R.string.course_navigation_course
1 -> R.string.course_navigation_video
2 -> R.string.course_navigation_discussion
3 -> R.string.course_navigation_dates
else -> R.string.course_navigation_handouts
}
Tabs.values().find { it.ordinal == position }?.titleResId
?: R.string.course_navigation_course
)
}.attach()
binding.tabLayout.isVisible = true

} else {
binding.viewPager.isUserInputEnabled = false
binding.bottomNavView.setOnItemSelectedListener {
when (it.itemId) {
R.id.outline -> {
viewModel.courseTabClickedEvent()
binding.viewPager.setCurrentItem(0, false)
}

R.id.videos -> {
viewModel.videoTabClickedEvent()
binding.viewPager.setCurrentItem(1, false)
}

R.id.discussions -> {
viewModel.discussionTabClickedEvent()
binding.viewPager.setCurrentItem(2, false)
}

R.id.dates -> {
viewModel.datesTabClickedEvent()
binding.viewPager.setCurrentItem(3, false)
}

R.id.resources -> {
viewModel.handoutsTabClickedEvent()
binding.viewPager.setCurrentItem(4, false)
}
binding.bottomNavView.setOnItemSelectedListener { menuItem ->
Tabs.values().find { menuItem.itemId == it.itemId }?.let { tab ->
viewModel.courseContainerTabClickedEvent(tab)
binding.viewPager.setCurrentItem(tab.ordinal, false)
}
true
}
Expand All @@ -155,6 +145,18 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
viewModel.updateData(withSwipeRefresh)
}

fun updateCourseDates() {
adapter?.getFragment(Tabs.DATES)?.let {
(it as CourseDatesFragment).updateData()
}
}

fun navigateToTab(tab: CourseContainerTab) {
adapter?.getFragment(tab)?.let {
binding.viewPager.setCurrentItem(tab.ordinal, true)
}
}

HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved
companion object {
private const val ARG_COURSE_ID = "courseId"
private const val ARG_TITLE = "title"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,33 @@ class CourseContainerViewModel(
}
}

fun courseTabClickedEvent() {
fun courseContainerTabClickedEvent(tab: CourseContainerTab) {
when (tab) {
CourseContainerTab.COURSE -> courseTabClickedEvent()
CourseContainerTab.VIDEOS -> videoTabClickedEvent()
CourseContainerTab.DISCUSSION -> discussionTabClickedEvent()
CourseContainerTab.DATES -> datesTabClickedEvent()
CourseContainerTab.HANDOUTS -> handoutsTabClickedEvent()
}
}

private fun courseTabClickedEvent() {
analytics.courseTabClickedEvent(courseId, courseName)
}

fun videoTabClickedEvent() {
private fun videoTabClickedEvent() {
analytics.videoTabClickedEvent(courseId, courseName)
}

fun discussionTabClickedEvent() {
private fun discussionTabClickedEvent() {
analytics.discussionTabClickedEvent(courseId, courseName)
}

fun datesTabClickedEvent() {
private fun datesTabClickedEvent() {
analytics.datesTabClickedEvent(courseId, courseName)
}

fun handoutsTabClickedEvent() {
private fun handoutsTabClickedEvent() {
analytics.handoutsTabClickedEvent(courseId, courseName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ import org.openedx.core.utils.TimeUtils
import org.openedx.core.utils.clearTime
import org.openedx.course.R
import org.openedx.course.presentation.CourseRouter
import org.openedx.course.presentation.container.CourseContainerFragment
import org.openedx.course.presentation.ui.CourseDatesBanner
import org.openedx.course.presentation.ui.CourseDatesBannerTablet
import org.openedx.core.R as coreR

class CourseDatesFragment : Fragment() {
Expand Down Expand Up @@ -156,13 +158,22 @@ class CourseDatesFragment : Fragment() {
}
},
onSyncDates = {
viewModel.resetCourseDatesBanner()
viewModel.resetCourseDatesBanner {
if (it) {
(parentFragment as CourseContainerFragment)
.updateCourseStructure(false)
}
}
},
)
}
}
}

fun updateData() {
HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved
viewModel.getCourseDates()
}

companion object {
private const val ARG_COURSE_ID = "courseId"
private const val ARG_IS_SELF_PACED = "selfPaced"
Expand Down Expand Up @@ -267,11 +278,19 @@ internal fun CourseDatesScreen(

if (courseBanner.isBannerAvailableForUserType(isSelfPaced)) {
item {
CourseDatesBanner(
modifier = Modifier.padding(bottom = 16.dp),
banner = courseBanner,
resetDates = onSyncDates
)
if (windowSize.isTablet) {
CourseDatesBannerTablet(
modifier = Modifier.padding(bottom = 16.dp),
banner = courseBanner,
resetDates = onSyncDates,
)
} else {
CourseDatesBanner(
modifier = Modifier.padding(bottom = 16.dp),
banner = courseBanner,
resetDates = onSyncDates
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ class CourseDatesViewModel(
}
}

fun resetCourseDatesBanner() {
fun resetCourseDatesBanner(onResetDates: (Boolean) -> Unit) {
viewModelScope.launch {
try {
interactor.resetCourseDates(courseId = courseId)
getCourseDates()
onResetDates(true)
} catch (e: Exception) {
if (e.isInternetError()) {
_uiMessage.value =
Expand All @@ -86,6 +87,7 @@ class CourseDatesViewModel(
_uiMessage.value =
UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error))
}
onResetDates(false)
}
}
}
Expand Down
Loading
Loading