From 4c1a90930a7275aa458874bbeab9ce55f327dcd2 Mon Sep 17 00:00:00 2001
From: PavloNetrebchuk <141041606+PavloNetrebchuk@users.noreply.github.com>
Date: Wed, 11 Sep 2024 12:09:22 +0300
Subject: [PATCH] feat: [FC-0047] Relative Dates (#367)
* feat: relative dates
* fix: Fixes according to designer feedback
---
.../app/data/storage/PreferencesManager.kt | 7 +
.../java/org/openedx/app/di/ScreenModule.kt | 6 +-
.../core/data/storage/CorePreferences.kt | 1 +
.../core/domain/model/CourseDateBlock.kt | 6 -
.../org/openedx/core/extension/StringExt.kt | 2 +-
.../java/org/openedx/core/utils/TimeUtils.kt | 190 ++++++------------
core/src/main/res/values/strings.xml | 17 +-
.../presentation/dates/CourseDatesScreen.kt | 34 ++--
.../dates/CourseDatesViewModel.kt | 3 +
.../outline/CourseOutlineScreen.kt | 7 +-
.../outline/CourseOutlineUIState.kt | 1 +
.../outline/CourseOutlineViewModel.kt | 3 +
.../course/presentation/ui/CourseUI.kt | 9 +-
.../course/presentation/ui/CourseVideosUI.kt | 6 +-
.../videos/CourseVideoViewModel.kt | 9 +-
.../videos/CourseVideosUIState.kt | 3 +-
.../dates/CourseDatesViewModelTest.kt | 7 +
.../outline/CourseOutlineViewModelTest.kt | 1 +
.../videos/CourseVideoViewModelTest.kt | 1 +
.../presentation/AllEnrolledCoursesView.kt | 2 +-
.../presentation/DashboardGalleryUIState.kt | 2 +-
.../presentation/DashboardGalleryView.kt | 15 +-
.../presentation/DashboardGalleryViewModel.kt | 14 +-
.../presentation/DashboardListFragment.kt | 2 +-
.../discovery/presentation/ui/DiscoveryUI.kt | 7 +-
.../presentation/calendar/CalendarFragment.kt | 7 +
.../calendar/CalendarSetUpView.kt | 9 +
.../calendar/CalendarSettingsView.kt | 8 +
.../presentation/calendar/CalendarUIState.kt | 3 +-
.../presentation/calendar/CalendarView.kt | 73 +++++++
.../calendar/CalendarViewModel.kt | 10 +-
profile/src/main/res/values/strings.xml | 1 +
32 files changed, 265 insertions(+), 201 deletions(-)
create mode 100644 profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarView.kt
diff --git a/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt b/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt
index efd2d16b2..1a4974a19 100644
--- a/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt
+++ b/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt
@@ -195,6 +195,12 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
}
get() = getString(CALENDAR_USER)
+ override var isRelativeDatesEnabled: Boolean
+ set(value) {
+ saveBoolean(IS_RELATIVE_DATES_ENABLED, value)
+ }
+ get() = getBoolean(IS_RELATIVE_DATES_ENABLED, true)
+
override var isHideInactiveCourses: Boolean
set(value) {
saveBoolean(HIDE_INACTIVE_COURSES, value)
@@ -225,6 +231,7 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
private const val CALENDAR_ID = "CALENDAR_ID"
private const val RESET_APP_DIRECTORY = "reset_app_directory"
private const val IS_CALENDAR_SYNC_ENABLED = "IS_CALENDAR_SYNC_ENABLED"
+ private const val IS_RELATIVE_DATES_ENABLED = "IS_RELATIVE_DATES_ENABLED"
private const val HIDE_INACTIVE_COURSES = "HIDE_INACTIVE_COURSES"
private const val CALENDAR_USER = "CALENDAR_USER"
}
diff --git a/app/src/main/java/org/openedx/app/di/ScreenModule.kt b/app/src/main/java/org/openedx/app/di/ScreenModule.kt
index 541782caf..15ef16498 100644
--- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt
+++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt
@@ -152,6 +152,7 @@ val screenModule = module {
get(),
get(),
get(),
+ get(),
windowSize
)
}
@@ -204,7 +205,7 @@ val screenModule = module {
)
}
viewModel { ManageAccountViewModel(get(), get(), get(), get(), get()) }
- viewModel { CalendarViewModel(get(), get(), get(), get(), get(), get(), get()) }
+ viewModel { CalendarViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { CoursesToSyncViewModel(get(), get(), get(), get()) }
viewModel { NewCalendarDialogViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { DisableCalendarSyncDialogViewModel(get(), get(), get(), get()) }
@@ -276,7 +277,7 @@ val screenModule = module {
get(),
get(),
get(),
- get()
+ get(),
)
}
viewModel { (courseId: String) ->
@@ -358,6 +359,7 @@ val screenModule = module {
get(),
get(),
get(),
+ get(),
)
}
viewModel { (courseId: String, handoutsType: String) ->
diff --git a/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt b/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt
index 7792fb4a4..5435494ba 100644
--- a/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt
+++ b/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt
@@ -13,6 +13,7 @@ interface CorePreferences {
var videoSettings: VideoSettings
var appConfig: AppConfig
var canResetAppDirectory: Boolean
+ var isRelativeDatesEnabled: Boolean
fun clearCorePreferences()
}
diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt b/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt
index 97f8612bf..9249d6a23 100644
--- a/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt
@@ -3,8 +3,6 @@ package org.openedx.core.domain.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.openedx.core.data.model.DateType
-import org.openedx.core.utils.isTimeLessThan24Hours
-import org.openedx.core.utils.isToday
import java.util.Date
@Parcelize
@@ -29,10 +27,6 @@ data class CourseDateBlock(
) && date.before(Date()))
}
- fun isTimeDifferenceLessThan24Hours(): Boolean {
- return (date.isToday() && date.before(Date())) || date.isTimeLessThan24Hours()
- }
-
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
diff --git a/core/src/main/java/org/openedx/core/extension/StringExt.kt b/core/src/main/java/org/openedx/core/extension/StringExt.kt
index 6d8457fed..d383cf57f 100644
--- a/core/src/main/java/org/openedx/core/extension/StringExt.kt
+++ b/core/src/main/java/org/openedx/core/extension/StringExt.kt
@@ -42,5 +42,5 @@ fun String.toImageLink(apiHostURL: String): String =
if (this.isLinkValid()) {
this
} else {
- apiHostURL + this.removePrefix("/")
+ (apiHostURL + this).replace(Regex("(? DateUtils.formatDateTime(
+ context,
+ date.time,
+ DateUtils.FORMAT_SHOW_WEEKDAY
+ ).toString()
+
+ daysDiff == -6 -> context.getString(R.string.core_next) + " " + DateUtils.formatDateTime(
+ context,
+ date.time,
+ DateUtils.FORMAT_SHOW_WEEKDAY
+ ).toString()
+
+ daysDiff in -1..1 -> DateUtils.getRelativeTimeSpanString(
+ date.time,
+ now.timeInMillis,
+ DateUtils.DAY_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_TIME
+ ).toString()
+
+ daysDiff in 2..6 -> DateUtils.getRelativeTimeSpanString(
+ date.time,
+ now.timeInMillis,
+ DateUtils.DAY_IN_MILLIS
+ ).toString()
+
+ inputDate.get(Calendar.YEAR) == now.get(Calendar.YEAR) -> {
+ DateUtils.getRelativeTimeSpanString(
+ date.time,
+ now.timeInMillis,
+ DateUtils.DAY_IN_MILLIS,
+ DateUtils.FORMAT_SHOW_DATE
+ ).toString()
+ }
+
+ else -> {
+ DateUtils.getRelativeTimeSpanString(
+ date.time,
+ now.timeInMillis,
+ DateUtils.DAY_IN_MILLIS,
+ DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
+ ).toString()
+ }
+ }
+ }
+
fun getCurrentTime(): Long {
return Calendar.getInstance().timeInMillis
}
@@ -170,126 +224,6 @@ object TimeUtils {
}
return formattedDate
}
-
- /**
- * Method to get the formatted time string in terms of relative time with minimum resolution of minutes.
- * For example, if the time difference is 1 minute, it will return "1m ago".
- *
- * @param date Date object to be formatted.
- */
- fun getFormattedTime(date: Date): String {
- return DateUtils.getRelativeTimeSpanString(
- date.time,
- getCurrentTime(),
- DateUtils.MINUTE_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_TIME
- ).toString()
- }
-
- /**
- * Returns a formatted date string for the given date.
- */
- fun getCourseFormattedDate(context: Context, date: Date): String {
- val inputDate = Calendar.getInstance().also {
- it.time = date
- it.clearTimeComponents()
- }
- val daysDifference = getDayDifference(inputDate)
-
- return when {
- daysDifference == 0 -> {
- context.getString(R.string.core_date_format_today)
- }
-
- daysDifference == 1 -> {
- context.getString(R.string.core_date_format_tomorrow)
- }
-
- daysDifference == -1 -> {
- context.getString(R.string.core_date_format_yesterday)
- }
-
- daysDifference in -2 downTo -7 -> {
- context.getString(
- R.string.core_date_format_days_ago,
- ceil(-daysDifference.toDouble()).toInt().toString()
- )
- }
-
- daysDifference in 2..7 -> {
- DateUtils.formatDateTime(
- context,
- date.time,
- DateUtils.FORMAT_SHOW_WEEKDAY
- )
- }
-
- inputDate.get(Calendar.YEAR) != Calendar.getInstance().get(Calendar.YEAR) -> {
- DateUtils.formatDateTime(
- context,
- date.time,
- DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
- )
- }
-
- else -> {
- DateUtils.formatDateTime(
- context,
- date.time,
- DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR
- )
- }
- }
- }
-
- fun getAssignmentFormattedDate(context: Context, date: Date): String {
- val inputDate = Calendar.getInstance().also {
- it.time = date
- it.clearTimeComponents()
- }
- val daysDifference = getDayDifference(inputDate)
-
- return when {
- daysDifference == 0 -> {
- context.getString(R.string.core_date_format_assignment_due_today)
- }
-
- daysDifference == 1 -> {
- context.getString(R.string.core_date_format_assignment_due_tomorrow)
- }
-
- daysDifference == -1 -> {
- context.getString(R.string.core_date_format_assignment_due_yesterday)
- }
-
- daysDifference <= -2 -> {
- val numberOfDays = ceil(-daysDifference.toDouble()).toInt()
- context.resources.getQuantityString(
- R.plurals.core_date_format_assignment_due_days_ago,
- numberOfDays,
- numberOfDays
- )
- }
-
- else -> {
- val numberOfDays = ceil(daysDifference.toDouble()).toInt()
- context.resources.getQuantityString(
- R.plurals.core_date_format_assignment_due_in,
- numberOfDays,
- numberOfDays
- )
- }
- }
- }
-
- /**
- * Returns the number of days difference between the given date and the current date.
- */
- private fun getDayDifference(inputDate: Calendar): Int {
- val currentDate = Calendar.getInstance().also { it.clearTimeComponents() }
- val difference = inputDate.timeInMillis - currentDate.timeInMillis
- return TimeUnit.MILLISECONDS.toDays(difference).toInt()
- }
}
/**
@@ -336,16 +270,6 @@ fun Date.clearTime(): Date {
return calendar.time
}
-/**
- * Extension function to check if the time difference between the given date and the current date is less than 24 hours.
- */
-fun Date.isTimeLessThan24Hours(): Boolean {
- val calendar = Calendar.getInstance()
- calendar.time = this
- val timeInMillis = (calendar.timeInMillis - TimeUtils.getCurrentTime()).unaryPlus()
- return timeInMillis < TimeUnit.DAYS.toMillis(1)
-}
-
fun Date.toCalendar(): Calendar {
val calendar = Calendar.getInstance()
calendar.time = this
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 00b02502a..0b245c7fa 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -92,21 +92,7 @@
Next Week
Upcoming
None
- Today
- Tomorrow
- Yesterday
- %1$s days ago
- Due Today
- Due Tomorrow
- Due Yesterday
-
- - Due %1$d day ago
- - Due %1$d days ago
-
-
- - Due in %1$d day
- - Due in %1$d days
-
+ Due %1$s
- %d Item Hidden
- %d Items Hidden
@@ -193,4 +179,5 @@
To Sync
Not Synced
Syncing to calendar…
+ Next
diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt
index b148c8acb..d76eb5eab 100644
--- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt
+++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt
@@ -74,7 +74,6 @@ import org.openedx.core.presentation.CoreAnalyticsScreen
import org.openedx.core.presentation.course.CourseViewMode
import org.openedx.core.presentation.dialog.alert.ActionDialogFragment
import org.openedx.core.presentation.settings.calendarsync.CalendarSyncState
-import org.openedx.core.presentation.settings.calendarsync.CalendarSyncUIState
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.WindowType
@@ -85,11 +84,12 @@ import org.openedx.core.ui.theme.appShapes
import org.openedx.core.ui.theme.appTypography
import org.openedx.core.ui.windowSizeValue
import org.openedx.core.utils.TimeUtils
+import org.openedx.core.utils.TimeUtils.formatToString
import org.openedx.core.utils.clearTime
import org.openedx.course.R
import org.openedx.course.presentation.ui.CourseDatesBanner
import org.openedx.course.presentation.ui.CourseDatesBannerTablet
-import java.util.concurrent.atomic.AtomicReference
+import java.util.Date
import org.openedx.core.R as CoreR
@Composable
@@ -109,6 +109,7 @@ fun CourseDatesScreen(
uiState = uiState,
uiMessage = uiMessage,
isSelfPaced = viewModel.isSelfPaced,
+ useRelativeDates = viewModel.useRelativeDates,
onItemClick = { block ->
if (block.blockId.isNotEmpty()) {
viewModel.getVerticalBlock(block.blockId)
@@ -178,6 +179,7 @@ private fun CourseDatesUI(
uiState: CourseDatesUIState,
uiMessage: UIMessage?,
isSelfPaced: Boolean,
+ useRelativeDates: Boolean,
onItemClick: (CourseDateBlock) -> Unit,
onPLSBannerViewed: () -> Unit,
onSyncDates: () -> Unit,
@@ -311,6 +313,7 @@ private fun CourseDatesUI(
sectionKey = DatesSection.COMPLETED,
sectionDates = section,
onItemClick = onItemClick,
+ useRelativeDates = useRelativeDates
)
}
}
@@ -325,6 +328,7 @@ private fun CourseDatesUI(
sectionKey = sectionKey,
sectionDates = section,
onItemClick = onItemClick,
+ useRelativeDates = useRelativeDates
)
}
}
@@ -420,6 +424,7 @@ fun CalendarSyncCard(
@Composable
fun ExpandableView(
sectionKey: DatesSection = DatesSection.NONE,
+ useRelativeDates: Boolean,
sectionDates: List,
onItemClick: (CourseDateBlock) -> Unit,
) {
@@ -503,6 +508,7 @@ fun ExpandableView(
sectionKey = sectionKey,
sectionDates = sectionDates,
onItemClick = onItemClick,
+ useRelativeDates = useRelativeDates
)
}
}
@@ -511,6 +517,7 @@ fun ExpandableView(
@Composable
private fun CourseDateBlockSection(
sectionKey: DatesSection = DatesSection.NONE,
+ useRelativeDates: Boolean,
sectionDates: List,
onItemClick: (CourseDateBlock) -> Unit,
) {
@@ -533,7 +540,7 @@ private fun CourseDateBlockSection(
if (sectionKey != DatesSection.COMPLETED) {
DateBullet(section = sectionKey)
}
- DateBlock(dateBlocks = sectionDates, onItemClick = onItemClick)
+ DateBlock(dateBlocks = sectionDates, onItemClick = onItemClick, useRelativeDates = useRelativeDates)
}
}
}
@@ -565,6 +572,7 @@ private fun DateBullet(
@Composable
private fun DateBlock(
dateBlocks: List,
+ useRelativeDates: Boolean,
onItemClick: (CourseDateBlock) -> Unit,
) {
Column(
@@ -579,7 +587,7 @@ private fun DateBlock(
if (index != 0) {
canShowDate = (lastAssignmentDate != dateBlock.date)
}
- CourseDateItem(dateBlock, canShowDate, index != 0, onItemClick)
+ CourseDateItem(dateBlock, canShowDate, index != 0, useRelativeDates, onItemClick)
lastAssignmentDate = dateBlock.date
}
}
@@ -590,8 +598,10 @@ private fun CourseDateItem(
dateBlock: CourseDateBlock,
canShowDate: Boolean,
isMiddleChild: Boolean,
+ useRelativeDates: Boolean,
onItemClick: (CourseDateBlock) -> Unit,
) {
+ val context = LocalContext.current
Column(
modifier = Modifier
.wrapContentHeight()
@@ -601,11 +611,7 @@ private fun CourseDateItem(
Spacer(modifier = Modifier.height(20.dp))
}
if (canShowDate) {
- val timeTitle = if (dateBlock.isTimeDifferenceLessThan24Hours()) {
- TimeUtils.getFormattedTime(dateBlock.date)
- } else {
- TimeUtils.getCourseFormattedDate(LocalContext.current, dateBlock.date)
- }
+ val timeTitle = formatToString(context, dateBlock.date, useRelativeDates)
Text(
text = timeTitle,
style = MaterialTheme.appTypography.labelMedium,
@@ -683,6 +689,7 @@ private fun CourseDatesScreenPreview() {
),
uiMessage = null,
isSelfPaced = true,
+ useRelativeDates = true,
onItemClick = {},
onPLSBannerViewed = {},
onSyncDates = {},
@@ -704,6 +711,7 @@ private fun CourseDatesScreenTabletPreview() {
),
uiMessage = null,
isSelfPaced = true,
+ useRelativeDates = true,
onItemClick = {},
onPLSBannerViewed = {},
onSyncDates = {},
@@ -743,7 +751,7 @@ private val mockedResponse: LinkedHashMap> =
CourseDateBlock(
title = "Homework 1: ABCD",
description = "After this date, course content will be archived",
- date = TimeUtils.iso8601ToDate("2023-10-20T15:08:07Z")!!,
+ date = Date(),
dateType = DateType.ASSIGNMENT_DUE_DATE,
)
)
@@ -793,9 +801,3 @@ private val mockedResponse: LinkedHashMap> =
)
)
)
-
-val mockCalendarSyncUIState = CalendarSyncUIState(
- isCalendarSyncEnabled = true,
- isSynced = true,
- checkForOutOfSync = AtomicReference()
-)
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 589c103fc..48fd0a524 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
@@ -14,6 +14,7 @@ import org.openedx.core.CalendarRouter
import org.openedx.core.R
import org.openedx.core.UIMessage
import org.openedx.core.config.Config
+import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.interactor.CalendarInteractor
import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.CourseBannerType
@@ -48,11 +49,13 @@ class CourseDatesViewModel(
private val config: Config,
private val calendarInteractor: CalendarInteractor,
private val calendarNotifier: CalendarNotifier,
+ private val corePreferences: CorePreferences,
val courseRouter: CourseRouter,
val calendarRouter: CalendarRouter
) : BaseViewModel() {
var isSelfPaced = true
+ var useRelativeDates = corePreferences.isRelativeDatesEnabled
private val _uiState = MutableStateFlow(CourseDatesUIState.Loading)
val uiState: StateFlow
diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt
index d40ae18b6..3b2ed4988 100644
--- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt
+++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt
@@ -315,6 +315,7 @@ private fun CourseOutlineUI(
modifier = listPadding.padding(vertical = 4.dp),
block = section,
onItemClick = onExpandClick,
+ useRelativeDates = uiState.useRelativeDates,
courseSectionsState = courseSectionsState,
courseSubSections = courseSubSections,
downloadedStateMap = uiState.downloadedState,
@@ -504,7 +505,8 @@ private fun CourseOutlineScreenPreview() {
verifiedUpgradeLink = "",
contentTypeGatingEnabled = false,
hasEnded = false
- )
+ ),
+ true
),
uiMessage = null,
onExpandClick = {},
@@ -537,7 +539,8 @@ private fun CourseOutlineScreenTabletPreview() {
verifiedUpgradeLink = "",
contentTypeGatingEnabled = false,
hasEnded = false
- )
+ ),
+ true
),
uiMessage = null,
onExpandClick = {},
diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineUIState.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineUIState.kt
index 0307b1f8e..389460442 100644
--- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineUIState.kt
+++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineUIState.kt
@@ -14,6 +14,7 @@ sealed class CourseOutlineUIState {
val courseSectionsState: Map,
val subSectionsDownloadsCount: Map,
val datesBannerInfo: CourseDatesBannerInfo,
+ val useRelativeDates: Boolean,
) : CourseOutlineUIState()
data object Loading : CourseOutlineUIState()
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 193b5c7e9..0acf4f64a 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
@@ -125,6 +125,7 @@ class CourseOutlineViewModel(
courseSectionsState = state.courseSectionsState,
subSectionsDownloadsCount = subSectionsDownloadsCount,
datesBannerInfo = state.datesBannerInfo,
+ useRelativeDates = preferencesManager.isRelativeDatesEnabled
)
}
}
@@ -158,6 +159,7 @@ class CourseOutlineViewModel(
courseSectionsState = courseSectionsState,
subSectionsDownloadsCount = subSectionsDownloadsCount,
datesBannerInfo = state.datesBannerInfo,
+ useRelativeDates = preferencesManager.isRelativeDatesEnabled
)
courseSectionsState[blockId] ?: false
@@ -215,6 +217,7 @@ class CourseOutlineViewModel(
courseSectionsState = courseSectionsState,
subSectionsDownloadsCount = subSectionsDownloadsCount,
datesBannerInfo = datesBannerInfo,
+ useRelativeDates = preferencesManager.isRelativeDatesEnabled
)
courseNotifier.send(CourseLoading(false))
} catch (e: Exception) {
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 f1bbe6086..780a7361d 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
@@ -591,6 +591,7 @@ fun VideoSubtitles(
fun CourseSection(
modifier: Modifier = Modifier,
block: Block,
+ useRelativeDates: Boolean,
onItemClick: (Block) -> Unit,
courseSectionsState: Boolean?,
courseSubSections: List?,
@@ -634,7 +635,8 @@ fun CourseSection(
) {
CourseSubSectionItem(
block = subSectionBlock,
- onClick = onSubSectionClick
+ onClick = onSubSectionClick,
+ useRelativeDates = useRelativeDates
)
}
}
@@ -745,6 +747,7 @@ fun CourseExpandableChapterCard(
fun CourseSubSectionItem(
modifier: Modifier = Modifier,
block: Block,
+ useRelativeDates: Boolean,
onClick: (Block) -> Unit,
) {
val context = LocalContext.current
@@ -753,7 +756,7 @@ fun CourseSubSectionItem(
val iconColor =
if (block.isCompleted()) MaterialTheme.appColors.successGreen else MaterialTheme.appColors.onSurface
val due by rememberSaveable {
- mutableStateOf(block.due?.let { TimeUtils.getAssignmentFormattedDate(context, it) })
+ mutableStateOf(block.due?.let { TimeUtils.formatToString(context, it, useRelativeDates) } ?: "")
}
val isAssignmentEnable = !block.isCompleted() && block.assignmentProgress != null && !due.isNullOrEmpty()
Column(
@@ -795,7 +798,7 @@ fun CourseSubSectionItem(
stringResource(
R.string.course_subsection_assignment_info,
block.assignmentProgress?.assignmentType ?: "",
- due ?: "",
+ stringResource(id = coreR.string.core_date_format_assignment_due, due),
block.assignmentProgress?.numPointsEarned?.toInt() ?: 0,
block.assignmentProgress?.numPointsPossible?.toInt() ?: 0
)
diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt
index 64022f498..73afb3d0b 100644
--- a/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt
+++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt
@@ -300,6 +300,7 @@ private fun CourseVideosUI(
courseSectionsState = courseSectionsState,
courseSubSections = courseSubSections,
downloadedStateMap = uiState.downloadedState,
+ useRelativeDates = uiState.useRelativeDates,
onSubSectionClick = onSubSectionClick,
onDownloadClick = onDownloadClick
)
@@ -632,7 +633,8 @@ private fun CourseVideosScreenPreview() {
remainingSize = 0,
allCount = 1,
allSize = 0
- )
+ ),
+ useRelativeDates = true
),
courseTitle = "",
onExpandClick = { },
@@ -689,7 +691,7 @@ private fun CourseVideosScreenTabletPreview() {
remainingSize = 0,
allCount = 0,
allSize = 0
- )
+ ), useRelativeDates = true
),
courseTitle = "",
onExpandClick = { },
diff --git a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt
index eb2c2d155..053d5a1f4 100644
--- a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt
@@ -168,8 +168,13 @@ class CourseVideoViewModel(
_uiState.value =
CourseVideosUIState.CourseData(
- courseStructure, getDownloadModelsStatus(), courseSubSections,
- courseSectionsState, subSectionsDownloadsCount, getDownloadModelsSize()
+ courseStructure = courseStructure,
+ downloadedState = getDownloadModelsStatus(),
+ courseSubSections = courseSubSections,
+ courseSectionsState = courseSectionsState,
+ subSectionsDownloadsCount = subSectionsDownloadsCount,
+ downloadModelsSize = getDownloadModelsSize(),
+ useRelativeDates = preferencesManager.isRelativeDatesEnabled
)
}
courseNotifier.send(CourseLoading(false))
diff --git a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosUIState.kt b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosUIState.kt
index ce05913d6..44f485c98 100644
--- a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosUIState.kt
+++ b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosUIState.kt
@@ -12,7 +12,8 @@ sealed class CourseVideosUIState {
val courseSubSections: Map>,
val courseSectionsState: Map,
val subSectionsDownloadsCount: Map,
- val downloadModelsSize: DownloadModelsSize
+ val downloadModelsSize: DownloadModelsSize,
+ val useRelativeDates: Boolean
) : CourseVideosUIState()
data class Empty(val message: String) : CourseVideosUIState()
diff --git a/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt
index 2fb055011..ed4e28f58 100644
--- a/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt
@@ -28,6 +28,7 @@ import org.openedx.core.R
import org.openedx.core.UIMessage
import org.openedx.core.config.Config
import org.openedx.core.data.model.DateType
+import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.interactor.CalendarInteractor
import org.openedx.core.domain.model.CourseCalendarState
import org.openedx.core.domain.model.CourseDateBlock
@@ -65,6 +66,7 @@ class CourseDatesViewModelTest {
private val calendarRouter = mockk()
private val calendarNotifier = mockk()
private val calendarInteractor = mockk()
+ private val preferencesManager = mockk()
private val openEdx = "OpenEdx"
private val noInternet = "Slow or no internet connection"
@@ -138,6 +140,7 @@ class CourseDatesViewModelTest {
coEvery { notifier.send(any()) } returns Unit
every { calendarNotifier.notifier } returns flowOf(CalendarSynced)
coEvery { calendarNotifier.send(any()) } returns Unit
+ every { preferencesManager.isRelativeDatesEnabled } returns true
coEvery { calendarInteractor.getCourseCalendarStateByIdFromCache(any()) } returns CourseCalendarState(
0,
"",
@@ -162,6 +165,7 @@ class CourseDatesViewModelTest {
config,
calendarInteractor,
calendarNotifier,
+ preferencesManager,
courseRouter,
calendarRouter,
)
@@ -191,6 +195,7 @@ class CourseDatesViewModelTest {
config,
calendarInteractor,
calendarNotifier,
+ preferencesManager,
courseRouter,
calendarRouter,
)
@@ -220,6 +225,7 @@ class CourseDatesViewModelTest {
config,
calendarInteractor,
calendarNotifier,
+ preferencesManager,
courseRouter,
calendarRouter,
)
@@ -249,6 +255,7 @@ class CourseDatesViewModelTest {
config,
calendarInteractor,
calendarNotifier,
+ preferencesManager,
courseRouter,
calendarRouter,
)
diff --git a/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt
index 15901d1b3..255cc6379 100644
--- a/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt
@@ -234,6 +234,7 @@ class CourseOutlineViewModelTest {
every { resourceManager.getString(org.openedx.course.R.string.course_can_download_only_with_wifi) } returns cantDownload
every { config.getApiHostURL() } returns "http://localhost:8000"
every { downloadDialogManager.showDownloadFailedPopup(any(), any()) } returns Unit
+ every { preferencesManager.isRelativeDatesEnabled } returns true
coEvery { interactor.getCourseDates(any()) } returns mockedCourseDatesResult
}
diff --git a/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt
index b8a4d543c..562bca77b 100644
--- a/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt
@@ -198,6 +198,7 @@ class CourseVideoViewModelTest {
Dispatchers.setMain(dispatcher)
every { config.getApiHostURL() } returns "http://localhost:8000"
every { courseNotifier.notifier } returns flowOf(CourseLoading(false))
+ every { preferencesManager.isRelativeDatesEnabled } returns true
every { downloadDialogManager.showPopup(any(), any(), any(), any(), any(), any(), any()) } returns Unit
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt
index e7e22ba1c..ef583112b 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt
@@ -419,7 +419,7 @@ fun CourseItem(
Column {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data(course.course.courseImage.toImageLink(apiHostUrl) ?: "")
+ .data(course.course.courseImage.toImageLink(apiHostUrl))
.error(R.drawable.core_no_image_course)
.placeholder(R.drawable.core_no_image_course)
.build(),
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryUIState.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryUIState.kt
index c4049f463..fdbc5d5db 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryUIState.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryUIState.kt
@@ -3,7 +3,7 @@ package org.openedx.courses.presentation
import org.openedx.core.domain.model.CourseEnrollments
sealed class DashboardGalleryUIState {
- data class Courses(val userCourses: CourseEnrollments) : DashboardGalleryUIState()
+ data class Courses(val userCourses: CourseEnrollments, val useRelativeDates: Boolean) : DashboardGalleryUIState()
data object Empty : DashboardGalleryUIState()
data object Loading : DashboardGalleryUIState()
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt
index 5de4c78c5..0fd0e2ccd 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt
@@ -213,6 +213,7 @@ private fun DashboardGalleryView(
UserCourses(
modifier = Modifier.fillMaxSize(),
userCourses = uiState.userCourses,
+ useRelativeDates = uiState.useRelativeDates,
apiHostUrl = apiHostUrl,
openCourse = {
onAction(DashboardGalleryScreenAction.OpenCourse(it))
@@ -274,6 +275,7 @@ private fun UserCourses(
modifier: Modifier = Modifier,
userCourses: CourseEnrollments,
apiHostUrl: String,
+ useRelativeDates: Boolean,
openCourse: (EnrolledCourse) -> Unit,
navigateToDates: (EnrolledCourse) -> Unit,
onViewAllClick: () -> Unit,
@@ -290,7 +292,8 @@ private fun UserCourses(
apiHostUrl = apiHostUrl,
navigateToDates = navigateToDates,
resumeBlockId = resumeBlockId,
- openCourse = openCourse
+ openCourse = openCourse,
+ useRelativeDates = useRelativeDates
)
}
if (userCourses.enrollments.courses.isNotEmpty()) {
@@ -505,6 +508,7 @@ private fun AssignmentItem(
private fun PrimaryCourseCard(
primaryCourse: EnrolledCourse,
apiHostUrl: String,
+ useRelativeDates: Boolean,
navigateToDates: (EnrolledCourse) -> Unit,
resumeBlockId: (enrolledCourse: EnrolledCourse, blockId: String) -> Unit,
openCourse: (EnrolledCourse) -> Unit,
@@ -527,7 +531,7 @@ private fun PrimaryCourseCard(
) {
AsyncImage(
model = ImageRequest.Builder(context)
- .data(apiHostUrl + primaryCourse.course.courseImage)
+ .data(primaryCourse.course.courseImage.toImageLink(apiHostUrl))
.error(CoreR.drawable.core_no_image_course)
.placeholder(CoreR.drawable.core_no_image_course)
.build(),
@@ -597,7 +601,10 @@ private fun PrimaryCourseCard(
info = stringResource(
R.string.dashboard_assignment_due,
nearestAssignment.assignmentType ?: "",
- TimeUtils.getAssignmentFormattedDate(context, nearestAssignment.date)
+ stringResource(
+ id = CoreR.string.core_date_format_assignment_due,
+ TimeUtils.formatToString(context, nearestAssignment.date, useRelativeDates)
+ )
)
)
}
@@ -856,7 +863,7 @@ private fun ViewAllItemPreview() {
private fun DashboardGalleryViewPreview() {
OpenEdXTheme {
DashboardGalleryView(
- uiState = DashboardGalleryUIState.Courses(mockUserCourses),
+ uiState = DashboardGalleryUIState.Courses(mockUserCourses, true),
apiHostUrl = "",
uiMessage = null,
updating = false,
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt
index 7f1036e1d..fdef55ee7 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt
@@ -14,6 +14,7 @@ import org.openedx.core.R
import org.openedx.core.UIMessage
import org.openedx.core.config.Config
import org.openedx.core.data.model.CourseEnrollments
+import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.EnrolledCourse
import org.openedx.core.extension.isInternetError
import org.openedx.core.system.ResourceManager
@@ -34,7 +35,8 @@ class DashboardGalleryViewModel(
private val networkConnection: NetworkConnection,
private val fileUtil: FileUtil,
private val dashboardRouter: DashboardRouter,
- private val windowSize: WindowSize
+ private val corePreferences: CorePreferences,
+ private val windowSize: WindowSize,
) : BaseViewModel() {
val apiHostUrl get() = config.getApiHostURL()
@@ -76,7 +78,10 @@ class DashboardGalleryViewModel(
if (response.primary == null && response.enrollments.courses.isEmpty()) {
_uiState.value = DashboardGalleryUIState.Empty
} else {
- _uiState.value = DashboardGalleryUIState.Courses(response)
+ _uiState.value = DashboardGalleryUIState.Courses(
+ response,
+ corePreferences.isRelativeDatesEnabled
+ )
}
} else {
val courseEnrollments = fileUtil.getObjectFromFile()
@@ -84,7 +89,10 @@ class DashboardGalleryViewModel(
_uiState.value = DashboardGalleryUIState.Empty
} else {
_uiState.value =
- DashboardGalleryUIState.Courses(courseEnrollments.mapToDomain())
+ DashboardGalleryUIState.Courses(
+ courseEnrollments.mapToDomain(),
+ corePreferences.isRelativeDatesEnabled
+ )
}
}
} catch (e: Exception) {
diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt
index 579076b96..fefcde867 100644
--- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt
+++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt
@@ -392,7 +392,7 @@ private fun CourseItem(
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data(enrolledCourse.course.courseImage.toImageLink(apiHostUrl) ?: "")
+ .data(enrolledCourse.course.courseImage.toImageLink(apiHostUrl))
.error(CoreR.drawable.core_no_image_course)
.placeholder(CoreR.drawable.core_no_image_course)
.build(),
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt b/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt
index 5d0f527bb..4ce446e31 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt
@@ -68,15 +68,10 @@ fun ImageHeader(
} else {
ContentScale.Crop
}
- val imageUrl = if (courseImage?.isLinkValid() == true) {
- courseImage
- } else {
- apiHostUrl + courseImage
- }
Box(modifier = modifier, contentAlignment = Alignment.Center) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data(imageUrl)
+ .data(courseImage?.toImageLink(apiHostUrl))
.error(CoreR.drawable.core_no_image_course)
.placeholder(CoreR.drawable.core_no_image_course)
.build(),
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarFragment.kt
index 112a4e774..fcc6db153 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarFragment.kt
@@ -59,6 +59,9 @@ class CalendarFragment : Fragment() {
onCalendarSyncSwitchClick = {
viewModel.setCalendarSyncEnabled(it, requireActivity().supportFragmentManager)
},
+ onRelativeDateSwitchClick = {
+ viewModel.setRelativeDateEnabled(it)
+ },
onChangeSyncOptionClick = {
val dialog = NewCalendarDialogFragment.newInstance(NewCalendarDialogType.UPDATE)
dialog.show(
@@ -84,11 +87,14 @@ private fun CalendarView(
onChangeSyncOptionClick: () -> Unit,
onCourseToSyncClick: () -> Unit,
onCalendarSyncSwitchClick: (Boolean) -> Unit,
+ onRelativeDateSwitchClick: (Boolean) -> Unit
) {
if (!uiState.isCalendarExist) {
CalendarSetUpView(
windowSize = windowSize,
+ useRelativeDates = uiState.isRelativeDateEnabled,
setUpCalendarSync = setUpCalendarSync,
+ onRelativeDateSwitchClick = onRelativeDateSwitchClick,
onBackClick = onBackClick
)
} else {
@@ -97,6 +103,7 @@ private fun CalendarView(
uiState = uiState,
onBackClick = onBackClick,
onCalendarSyncSwitchClick = onCalendarSyncSwitchClick,
+ onRelativeDateSwitchClick = onRelativeDateSwitchClick,
onChangeSyncOptionClick = onChangeSyncOptionClick,
onCourseToSyncClick = onCourseToSyncClick
)
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt
index 06a842630..7309a42f9 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt
@@ -55,7 +55,9 @@ import org.openedx.profile.R
@Composable
fun CalendarSetUpView(
windowSize: WindowSize,
+ useRelativeDates: Boolean,
setUpCalendarSync: () -> Unit,
+ onRelativeDateSwitchClick: (Boolean) -> Unit,
onBackClick: () -> Unit
) {
val scaffoldState = rememberScaffoldState()
@@ -192,6 +194,11 @@ fun CalendarSetUpView(
Spacer(modifier = Modifier.height(24.dp))
}
}
+ Spacer(modifier = Modifier.height(28.dp))
+ OptionsSection(
+ isRelativeDatesEnabled = useRelativeDates,
+ onRelativeDateSwitchClick = onRelativeDateSwitchClick
+ )
}
}
}
@@ -206,7 +213,9 @@ private fun CalendarScreenPreview() {
OpenEdXTheme {
CalendarSetUpView(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
+ useRelativeDates = true,
setUpCalendarSync = {},
+ onRelativeDateSwitchClick = { _ -> },
onBackClick = {}
)
}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSettingsView.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSettingsView.kt
index bce3ede77..d8c2e9a55 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSettingsView.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSettingsView.kt
@@ -67,6 +67,7 @@ fun CalendarSettingsView(
windowSize: WindowSize,
uiState: CalendarUIState,
onCalendarSyncSwitchClick: (Boolean) -> Unit,
+ onRelativeDateSwitchClick: (Boolean) -> Unit,
onChangeSyncOptionClick: () -> Unit,
onCourseToSyncClick: () -> Unit,
onBackClick: () -> Unit
@@ -155,6 +156,11 @@ fun CalendarSettingsView(
onCourseToSyncClick = onCourseToSyncClick
)
}
+ Spacer(modifier = Modifier.height(32.dp))
+ OptionsSection(
+ isRelativeDatesEnabled = uiState.isRelativeDateEnabled,
+ onRelativeDateSwitchClick = onRelativeDateSwitchClick
+ )
}
}
}
@@ -312,10 +318,12 @@ private fun CalendarSettingsViewPreview() {
calendarData = CalendarData("calendar", Color.Red.toArgb()),
calendarSyncState = CalendarSyncState.SYNCED,
isCalendarSyncEnabled = false,
+ isRelativeDateEnabled = true,
coursesSynced = 5
),
onBackClick = {},
onCalendarSyncSwitchClick = {},
+ onRelativeDateSwitchClick = {},
onChangeSyncOptionClick = {},
onCourseToSyncClick = {}
)
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarUIState.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarUIState.kt
index cf99e0fa2..513a5c5e5 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarUIState.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarUIState.kt
@@ -8,5 +8,6 @@ data class CalendarUIState(
val calendarData: CalendarData? = null,
val calendarSyncState: CalendarSyncState,
val isCalendarSyncEnabled: Boolean,
- val coursesSynced: Int?
+ val coursesSynced: Int?,
+ val isRelativeDateEnabled: Boolean,
)
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarView.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarView.kt
new file mode 100644
index 000000000..4cc682dc7
--- /dev/null
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarView.kt
@@ -0,0 +1,73 @@
+package org.openedx.profile.presentation.calendar
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Switch
+import androidx.compose.material.SwitchDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import org.openedx.core.ui.theme.appColors
+import org.openedx.core.ui.theme.appTypography
+import org.openedx.core.utils.TimeUtils
+import org.openedx.profile.R
+import java.util.Date
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun OptionsSection(
+ isRelativeDatesEnabled: Boolean,
+ onRelativeDateSwitchClick: (Boolean) -> Unit
+) {
+ val context = LocalContext.current
+ val textDescription = if (isRelativeDatesEnabled) {
+ stringResource(R.string.profile_show_relative_dates)
+ } else {
+ stringResource(
+ R.string.profile_show_full_dates,
+ TimeUtils.formatToString(context, Date(), false)
+ )
+ }
+ Column {
+ SectionTitle(stringResource(R.string.profile_options))
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ modifier = Modifier.weight(1f),
+ text = stringResource(R.string.profile_use_relative_dates),
+ style = MaterialTheme.appTypography.titleMedium,
+ color = MaterialTheme.appColors.textDark
+ )
+ CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
+ Switch(
+ modifier = Modifier
+ .padding(0.dp),
+ checked = isRelativeDatesEnabled,
+ onCheckedChange = onRelativeDateSwitchClick,
+ colors = SwitchDefaults.colors(
+ checkedThumbColor = MaterialTheme.appColors.textAccent
+ )
+ )
+ }
+ }
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = textDescription,
+ style = MaterialTheme.appTypography.labelMedium,
+ color = MaterialTheme.appColors.textPrimaryVariant
+ )
+ }
+}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt
index 658d7ca8e..c50bf587c 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt
@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.openedx.core.BaseViewModel
import org.openedx.core.data.storage.CalendarPreferences
+import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.interactor.CalendarInteractor
import org.openedx.core.presentation.settings.calendarsync.CalendarSyncState
import org.openedx.core.system.CalendarManager
@@ -30,6 +31,7 @@ class CalendarViewModel(
private val calendarPreferences: CalendarPreferences,
private val calendarNotifier: CalendarNotifier,
private val calendarInteractor: CalendarInteractor,
+ private val corePreferences: CorePreferences,
private val profileRouter: ProfileRouter,
private val networkConnection: NetworkConnection,
) : BaseViewModel() {
@@ -40,7 +42,8 @@ class CalendarViewModel(
calendarData = null,
calendarSyncState = if (networkConnection.isOnline()) CalendarSyncState.SYNCED else CalendarSyncState.OFFLINE,
isCalendarSyncEnabled = calendarPreferences.isCalendarSyncEnabled,
- coursesSynced = null
+ coursesSynced = null,
+ isRelativeDateEnabled = corePreferences.isRelativeDatesEnabled,
)
private val _uiState = MutableStateFlow(calendarInitState)
@@ -107,6 +110,11 @@ class CalendarViewModel(
}
}
+ fun setRelativeDateEnabled(isEnabled: Boolean) {
+ corePreferences.isRelativeDatesEnabled = isEnabled
+ _uiState.update { it.copy(isRelativeDateEnabled = isEnabled) }
+ }
+
fun navigateToCoursesToSync(fragmentManager: FragmentManager) {
profileRouter.navigateToCoursesToSync(fragmentManager)
}
diff --git a/profile/src/main/res/values/strings.xml b/profile/src/main/res/values/strings.xml
index 41535240c..1adf22c97 100644
--- a/profile/src/main/res/values/strings.xml
+++ b/profile/src/main/res/values/strings.xml
@@ -78,5 +78,6 @@
No %1$s Courses
No courses are currently being synced to your calendar.
No courses match the current filter.
+ Show full dates like “%1$s”