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 1 commit
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): CourseDates
HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved

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

Expand Down
18 changes: 11 additions & 7 deletions core/src/main/java/org/openedx/core/data/model/CourseDates.kt
HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ data class CourseDates(
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(),
)
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).getDatesBannerInfo()

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 @@ -14,4 +14,6 @@ class CourseContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment
fun addFragment(fragment: Fragment) {
fragments.add(fragment)
}

fun getFragment(position: Int): Fragment = fragments[position]
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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.core.R as coreR

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

Expand Down Expand Up @@ -155,6 +156,30 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
viewModel.updateData(withSwipeRefresh)
}

fun showDatesUpdateSnackbar(isSuccess: Boolean) {
val message = if (isSuccess) {
getString(coreR.string.core_dates_shift_dates_successfully_msg)
} else {
getString(coreR.string.core_dates_shift_dates_unsuccessful_msg)
}
val duration = if (isSuccess) {
Snackbar.LENGTH_INDEFINITE
} else {
Snackbar.LENGTH_LONG
}
snackBar = Snackbar.make(binding.root, message, duration).also {
if (isSuccess) {
it.setAction(coreR.string.core_dates_view_all_dates) {
binding.viewPager.setCurrentItem(3, true)
k1rill marked this conversation as resolved.
Show resolved Hide resolved
adapter?.getFragment(3)?.let { fragment ->
(fragment as CourseDatesFragment).updateData()
}
}
}
}
snackBar?.show()
}

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 @@ -163,6 +163,10 @@ class CourseDatesFragment : Fragment() {
}
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ class CourseInfoFragment : Fragment() {
LaunchedEffect(showAlert) {
if (showAlert) {
InfoDialogFragment.newInstance(
title = context.getString(CoreR.string.course_enrollment_error),
title = context.getString(CoreR.string.core_enrollment_error),
message = context.getString(
CoreR.string.course_enrollment_error_message,
CoreR.string.core_enrollment_error_message,
getString(CoreR.string.platform_name)
)
).show(
Expand Down
HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,41 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.*
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
Expand All @@ -37,16 +62,27 @@ import org.openedx.core.R
import org.openedx.core.UIMessage
import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.BlockCounts
import org.openedx.core.domain.model.CourseDatesBannerInfo
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.domain.model.CoursewareAccess
import org.openedx.core.presentation.course.CourseViewMode
import org.openedx.core.ui.*
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.OfflineModeDialog
import org.openedx.core.ui.OpenEdXButton
import org.openedx.core.ui.TextIcon
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.WindowType
import org.openedx.core.ui.displayCutoutForLandscape
import org.openedx.core.ui.rememberWindowSize
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appTypography
import org.openedx.core.ui.windowSizeValue
import org.openedx.course.presentation.CourseRouter
import org.openedx.course.presentation.container.CourseContainerFragment
import org.openedx.course.presentation.outline.CourseOutlineFragment.Companion.getUnitBlockIcon
import org.openedx.course.presentation.ui.CourseDatesBanner
import org.openedx.course.presentation.ui.CourseDatesBannerTablet
import org.openedx.course.presentation.ui.CourseExpandableChapterCard
import org.openedx.course.presentation.ui.CourseImageHeader
import org.openedx.course.presentation.ui.CourseSectionCard
Expand Down Expand Up @@ -153,6 +189,11 @@ class CourseOutlineFragment : Fragment() {
.replace(Regex("\\s"), "_"), it.id
)
}
},
onResetDatesClick = {
viewModel.resetCourseDatesBanner(onResetDates = {
(parentFragment as CourseContainerFragment).showDatesUpdateSnackbar(it)
})
}
)
}
Expand Down Expand Up @@ -204,7 +245,8 @@ internal fun CourseOutlineScreen(
onExpandClick: (Block) -> Unit,
onSubSectionClick: (Block) -> Unit,
onResumeClick: (String) -> Unit,
onDownloadClick: (Block) -> Unit
onDownloadClick: (Block) -> Unit,
onResetDatesClick: () -> Unit,
) {
val scaffoldState = rememberScaffoldState()
val pullRefreshState =
Expand Down Expand Up @@ -291,6 +333,30 @@ internal fun CourseOutlineScreen(
)
}
}
uiState.datesBannerInfo?.let {
if (it.isBannerAvailableForDashboard()) {
HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved
item {
Box(
modifier = Modifier
.padding(all = 6.dp)
HamzaIsrar12 marked this conversation as resolved.
Show resolved Hide resolved
) {
if (windowSize.isTablet) {
CourseDatesBannerTablet(
modifier = Modifier.padding(bottom = 16.dp),
banner = it,
resetDates = onResetDatesClick,
)
} else {
CourseDatesBanner(
modifier = Modifier.padding(bottom = 16.dp),
banner = it,
resetDates = onResetDatesClick,
)
}
}
}
}
}
if (uiState.resumeComponent != null) {
item {
Spacer(Modifier.height(28.dp))
Expand Down Expand Up @@ -503,7 +569,7 @@ private fun ResumeCourseTablet(
}
}
OpenEdXButton(
width = Modifier.width(194.dp),
width = Modifier.width(210.dp),
text = stringResource(id = org.openedx.course.R.string.course_resume),
onClick = {
onResumeClick(block.id)
Expand Down Expand Up @@ -533,7 +599,8 @@ private fun CourseOutlineScreenPreview() {
mockChapterBlock,
mapOf(),
mapOf(),
mapOf()
mapOf(),
CourseDatesBannerInfo(false, false, "", false, false)
),
apiHostUrl = "",
isCourseNestedListEnabled = true,
Expand All @@ -547,7 +614,8 @@ private fun CourseOutlineScreenPreview() {
onSubSectionClick = {},
onResumeClick = {},
onReloadClick = {},
onDownloadClick = {}
onDownloadClick = {},
onResetDatesClick = {},
)
}
}
Expand All @@ -565,7 +633,8 @@ private fun CourseOutlineScreenTabletPreview() {
mockChapterBlock,
mapOf(),
mapOf(),
mapOf()
mapOf(),
CourseDatesBannerInfo(false, false, "", false, false)
),
apiHostUrl = "",
isCourseNestedListEnabled = true,
Expand All @@ -579,7 +648,8 @@ private fun CourseOutlineScreenTabletPreview() {
onSubSectionClick = {},
onResumeClick = {},
onReloadClick = {},
onDownloadClick = {}
onDownloadClick = {},
onResetDatesClick = {},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.openedx.course.presentation.outline

import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.CourseDatesBannerInfo
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.module.db.DownloadedState

Expand All @@ -11,7 +12,8 @@ sealed class CourseOutlineUIState {
val resumeComponent: Block?,
val courseSubSections: Map<String, List<Block>>,
val courseSectionsState: Map<String, Boolean>,
val subSectionsDownloadsCount: Map<String, Int>
val subSectionsDownloadsCount: Map<String, Int>,
val datesBannerInfo: CourseDatesBannerInfo?,
) : CourseOutlineUIState()

object Loading : CourseOutlineUIState()
Expand Down
Loading
Loading