diff --git a/core/src/main/java/org/openedx/core/UIMessage.kt b/core/src/main/java/org/openedx/core/UIMessage.kt index 6f26d6f65..8a9267f36 100644 --- a/core/src/main/java/org/openedx/core/UIMessage.kt +++ b/core/src/main/java/org/openedx/core/UIMessage.kt @@ -2,11 +2,11 @@ package org.openedx.core import androidx.compose.material.SnackbarDuration -sealed class UIMessage { +open class UIMessage { class SnackBarMessage( val message: String, val duration: SnackbarDuration = SnackbarDuration.Long, ) : UIMessage() class ToastMessage(val message: String) : UIMessage() -} \ No newline at end of file +} diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 27c109b26..c2c9927c3 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -107,9 +107,10 @@ 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. To complete graded assignments as part of this course, you can upgrade today. 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. + Due Dates Shifted Your due dates have been successfully shifted to help you stay on track. Your dates could not be shifted. Please try again. - View all dates + View All Dates Register Sign in diff --git a/course/src/main/java/org/openedx/course/DatesShiftedSnackBar.kt b/course/src/main/java/org/openedx/course/DatesShiftedSnackBar.kt new file mode 100644 index 000000000..fd2a3ce6b --- /dev/null +++ b/course/src/main/java/org/openedx/course/DatesShiftedSnackBar.kt @@ -0,0 +1,5 @@ +package org.openedx.course + +import org.openedx.core.UIMessage + +class DatesShiftedSnackBar : UIMessage() diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesFragment.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesFragment.kt index 4b0e175c1..e906ef6a2 100644 --- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesFragment.kt @@ -37,6 +37,10 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarData +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState import androidx.compose.material.Surface import androidx.compose.material.Switch import androidx.compose.material.SwitchDefaults @@ -49,6 +53,7 @@ import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -99,12 +104,14 @@ import org.openedx.core.ui.theme.appTypography import org.openedx.core.ui.windowSizeValue import org.openedx.core.utils.TimeUtils import org.openedx.core.utils.clearTime +import org.openedx.course.DatesShiftedSnackBar import org.openedx.course.R import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.calendarsync.CalendarSyncUIState import org.openedx.course.presentation.container.CourseContainerFragment import org.openedx.course.presentation.ui.CourseDatesBanner import org.openedx.course.presentation.ui.CourseDatesBannerTablet +import org.openedx.course.presentation.ui.DatesShiftedSnackBar import java.util.concurrent.atomic.AtomicReference import org.openedx.core.R as coreR @@ -268,6 +275,16 @@ internal fun CourseDatesScreen( ) } + val snackState = remember { SnackbarHostState() } + if (uiMessage is DatesShiftedSnackBar) { + val datesShiftedMessage = stringResource(id = R.string.course_dates_shifted_message) + LaunchedEffect(uiMessage) { + snackState.showSnackbar( + message = datesShiftedMessage, + duration = SnackbarDuration.Long + ) + } + } HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState) Box( @@ -394,6 +411,15 @@ internal fun CourseDatesScreen( }) } } + + SnackbarHost( + modifier = Modifier.align(Alignment.BottomStart), + hostState = snackState + ) { snackbarData: SnackbarData -> + DatesShiftedSnackBar(onClose = { + snackbarData.dismiss() + }) + } } } } diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt index 6313b599d..0c1754552 100644 --- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.openedx.core.BaseViewModel +import org.openedx.core.R import org.openedx.core.SingleEventLiveData import org.openedx.core.UIMessage import org.openedx.core.config.Config @@ -22,6 +23,7 @@ import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent import org.openedx.core.system.notifier.CourseNotifier +import org.openedx.course.DatesShiftedSnackBar import org.openedx.course.domain.interactor.CourseInteractor import org.openedx.course.presentation.calendarsync.CalendarManager import org.openedx.course.presentation.calendarsync.CalendarSyncDialogType @@ -115,6 +117,7 @@ class CourseDatesViewModel( try { interactor.resetCourseDates(courseId = courseId) getCourseDates() + _uiMessage.value = DatesShiftedSnackBar() onResetDates(true) } catch (e: Exception) { if (e.isInternetError()) { @@ -122,7 +125,7 @@ class CourseDatesViewModel( UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_no_connection)) } else { _uiMessage.value = - UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_unknown_error)) + UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_dates_shift_dates_unsuccessful_msg)) } onResetDates(false) } diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt index c0f200b37..4d9dfc54b 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt @@ -28,6 +28,10 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarData +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.pullrefresh.PullRefreshIndicator @@ -35,6 +39,7 @@ import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -54,7 +59,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.Fragment -import com.google.android.material.snackbar.Snackbar import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -79,6 +83,7 @@ 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.DatesShiftedSnackBar import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.container.CourseContainerFragment import org.openedx.course.presentation.container.CourseContainerTab @@ -89,6 +94,7 @@ import org.openedx.course.presentation.ui.CourseExpandableChapterCard import org.openedx.course.presentation.ui.CourseImageHeader import org.openedx.course.presentation.ui.CourseSectionCard import org.openedx.course.presentation.ui.CourseSubSectionItem +import org.openedx.course.presentation.ui.DatesShiftedSnackBar import java.io.File import java.util.Date @@ -99,8 +105,6 @@ class CourseOutlineFragment : Fragment() { } private val router by inject() - private var snackBar: Snackbar? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycle.addObserver(viewModel) @@ -210,38 +214,16 @@ class CourseOutlineFragment : Fragment() { onResetDatesClick = { viewModel.resetCourseDatesBanner(onResetDates = { (parentFragment as CourseContainerFragment).updateCourseDates() - showDatesUpdateSnackbar(it) }) - } - ) - } - } - } - - override fun onDestroyView() { - snackBar?.dismiss() - super.onDestroyView() - } - - private fun showDatesUpdateSnackbar(isSuccess: Boolean) { - val message = if (isSuccess) { - getString(R.string.core_dates_shift_dates_successfully_msg) - } else { - getString(R.string.core_dates_shift_dates_unsuccessful_msg) - } - snackBar = view?.let { - Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply { - if (isSuccess) { - setAction(R.string.core_dates_view_all_dates) { + }, + onViewDates = { (parentFragment as CourseContainerFragment).navigateToTab(CourseContainerTab.DATES) } - } + ) } } - snackBar?.show() } - companion object { private const val ARG_COURSE_ID = "courseId" private const val ARG_TITLE = "title" @@ -288,6 +270,7 @@ internal fun CourseOutlineScreen( onResumeClick: (String) -> Unit, onDownloadClick: (Block) -> Unit, onResetDatesClick: () -> Unit, + onViewDates: () -> Unit?, ) { val scaffoldState = rememberScaffoldState() val pullRefreshState = @@ -331,6 +314,17 @@ internal fun CourseOutlineScreen( ) } + val snackState = remember { SnackbarHostState() } + if (uiMessage is DatesShiftedSnackBar) { + val datesShiftedMessage = + stringResource(id = org.openedx.course.R.string.course_dates_shifted_message) + LaunchedEffect(uiMessage) { + snackState.showSnackbar( + message = datesShiftedMessage, + duration = SnackbarDuration.Long + ) + } + } HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState) Box( @@ -512,6 +506,17 @@ internal fun CourseOutlineScreen( ) } } + + SnackbarHost( + modifier = Modifier.align(Alignment.BottomStart), + hostState = snackState + ) { snackbarData: SnackbarData -> + DatesShiftedSnackBar(showAction = true, + onViewDates = onViewDates, + onClose = { + snackbarData.dismiss() + }) + } } } } @@ -661,6 +666,7 @@ private fun CourseOutlineScreenPreview() { onReloadClick = {}, onDownloadClick = {}, onResetDatesClick = {}, + onViewDates = {}, ) } } @@ -701,6 +707,7 @@ private fun CourseOutlineScreenTabletPreview() { onReloadClick = {}, onDownloadClick = {}, onResetDatesClick = {}, + onViewDates = {}, ) } } diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt index 0337f811b..b129fcd13 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt @@ -27,6 +27,7 @@ import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent import org.openedx.core.system.notifier.CourseNotifier import org.openedx.core.system.notifier.CourseStructureUpdated +import org.openedx.course.DatesShiftedSnackBar import org.openedx.course.domain.interactor.CourseInteractor import org.openedx.course.presentation.CourseAnalytics import org.openedx.course.presentation.calendarsync.CalendarSyncDialogType @@ -270,6 +271,7 @@ class CourseOutlineViewModel( try { interactor.resetCourseDates(courseId = courseId) updateCourseData(false) + _uiMessage.value = DatesShiftedSnackBar() onResetDates(true) } catch (e: Exception) { if (e.isInternetError()) { @@ -277,7 +279,7 @@ class CourseOutlineViewModel( UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)) } else { _uiMessage.value = - UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)) + UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_dates_shift_dates_unsuccessful_msg)) } onResetDates(false) } diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt index e37c04175..23c31e843 100644 --- a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt +++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt @@ -40,6 +40,7 @@ import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton import androidx.compose.material.Scaffold +import androidx.compose.material.Snackbar import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.icons.Icons @@ -113,7 +114,7 @@ import org.openedx.course.presentation.outline.CourseOutlineFragment import subtitleFile.Caption import subtitleFile.TimedTextObject import java.util.Date -import org.openedx.course.R as courseR +import org.openedx.core.R as coreR @Composable fun CourseImageHeader( @@ -141,8 +142,8 @@ fun CourseImageHeader( AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(imageUrl) - .error(org.openedx.core.R.drawable.core_no_image_course) - .placeholder(org.openedx.core.R.drawable.core_no_image_course) + .error(coreR.drawable.core_no_image_course) + .placeholder(coreR.drawable.core_no_image_course) .build(), contentDescription = stringResource( id = R.string.course_accessibility_header_image_for, @@ -450,9 +451,9 @@ fun NavigationUnitsButtons( onNextClick: () -> Unit ) { val nextButtonIcon = if (hasNextBlock) { - painterResource(id = org.openedx.core.R.drawable.core_ic_down) + painterResource(id = coreR.drawable.core_ic_down) } else { - painterResource(id = org.openedx.core.R.drawable.core_ic_check_in_box) + painterResource(id = coreR.drawable.core_ic_check_in_box) } val subModifier = @@ -499,7 +500,7 @@ fun NavigationUnitsButtons( Spacer(Modifier.width(8.dp)) Icon( modifier = Modifier.rotate(if (isVerticalNavigation) 0f else -90f), - painter = painterResource(id = org.openedx.core.R.drawable.core_ic_up), + painter = painterResource(id = coreR.drawable.core_ic_up), contentDescription = null, tint = MaterialTheme.appColors.primary ) @@ -666,7 +667,7 @@ fun VideoSubtitles( horizontalArrangement = Arrangement.SpaceBetween ) { Text( - text = stringResource(id = courseR.string.course_subtitles), + text = stringResource(id = R.string.course_subtitles), color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.titleMedium ) @@ -676,7 +677,7 @@ fun VideoSubtitles( onSettingsClick() }, text = subtitleLanguage, - painter = painterResource(id = courseR.drawable.course_ic_cc), + painter = painterResource(id = R.drawable.course_ic_cc), color = MaterialTheme.appColors.textAccent, textStyle = MaterialTheme.appTypography.labelLarge ) @@ -1160,6 +1161,59 @@ fun CourseDatesBannerTablet( } } +@Composable +fun DatesShiftedSnackBar( + showAction: Boolean = false, + onViewDates: () -> Unit? = {}, + onClose: () -> Unit? = {}, +) { + Snackbar( + modifier = Modifier.padding(16.dp), + backgroundColor = MaterialTheme.appColors.background + ) { + Column(modifier = Modifier.padding(4.dp)) { + Box { + Text( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterStart), + text = stringResource(id = coreR.string.core_dates_shift_dates_successfully_title), + color = MaterialTheme.appColors.textFieldText, + style = MaterialTheme.appTypography.titleMedium + ) + IconButton(modifier = Modifier.align(Alignment.TopEnd), onClick = { onClose() }) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = "close", + tint = MaterialTheme.appColors.onBackground, + ) + } + } + Text( + modifier = Modifier + .padding(top = 4.dp) + .fillMaxWidth(), + text = stringResource(id = coreR.string.core_dates_shift_dates_successfully_msg), + color = MaterialTheme.appColors.textFieldText, + style = MaterialTheme.appTypography.titleSmall, + ) + if (showAction) { + OpenEdXOutlinedButton( + modifier = Modifier + .padding(top = 16.dp) + .fillMaxWidth(), + text = stringResource(id = coreR.string.core_dates_view_all_dates), + backgroundColor = MaterialTheme.appColors.background, + textColor = MaterialTheme.appColors.primary, + borderColor = MaterialTheme.appColors.primary, + onClick = { + onViewDates() + }) + } + } + } +} + @Composable fun WarningLabel( painter: Painter, @@ -1219,7 +1273,7 @@ private fun WarningLabelPreview() { OpenEdXTheme { WarningLabel( painter = painterResource(id = org.openedx.core.R.drawable.core_ic_offline), - text = stringResource(id = courseR.string.course_no_internet_label) + text = stringResource(id = R.string.course_no_internet_label) ) } } diff --git a/course/src/main/res/values/strings.xml b/course/src/main/res/values/strings.xml index da5ebe36b..4a4ef80ed 100644 --- a/course/src/main/res/values/strings.xml +++ b/course/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ You are already enrolled in this course. Discover You cannot change the download video quality when all videos are downloading + Dates Shifted Course dates are not currently available.