Skip to content

Commit

Permalink
feat: [FC-0047] Relative Dates (openedx#367)
Browse files Browse the repository at this point in the history
* feat: relative dates

* fix: Fixes according to designer feedback
  • Loading branch information
PavloNetrebchuk authored Sep 11, 2024
1 parent d282fb0 commit 4c1a909
Show file tree
Hide file tree
Showing 32 changed files with 265 additions and 201 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
}
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ val screenModule = module {
get(),
get(),
get(),
get(),
windowSize
)
}
Expand Down Expand Up @@ -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()) }
Expand Down Expand Up @@ -276,7 +277,7 @@ val screenModule = module {
get(),
get(),
get(),
get()
get(),
)
}
viewModel { (courseId: String) ->
Expand Down Expand Up @@ -358,6 +359,7 @@ val screenModule = module {
get(),
get(),
get(),
get(),
)
}
viewModel { (courseId: String, handoutsType: String) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface CorePreferences {
var videoSettings: VideoSettings
var appConfig: AppConfig
var canResetAppDirectory: Boolean
var isRelativeDatesEnabled: Boolean

fun clearCorePreferences()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/openedx/core/extension/StringExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ fun String.toImageLink(apiHostURL: String): String =
if (this.isLinkValid()) {
this
} else {
apiHostURL + this.removePrefix("/")
(apiHostURL + this).replace(Regex("(?<!:)//"), "/")
}
190 changes: 57 additions & 133 deletions core/src/main/java/org/openedx/core/utils/TimeUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,76 @@ import com.google.gson.internal.bind.util.ISO8601Utils
import org.openedx.core.R
import org.openedx.core.domain.model.StartType
import org.openedx.core.system.ResourceManager
import java.text.DateFormat
import java.text.ParseException
import java.text.ParsePosition
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.math.ceil

object TimeUtils {

private const val FORMAT_ISO_8601 = "yyyy-MM-dd'T'HH:mm:ss'Z'"
private const val FORMAT_ISO_8601_WITH_TIME_ZONE = "yyyy-MM-dd'T'HH:mm:ssXXX"

private const val SEVEN_DAYS_IN_MILLIS = 604800000L

fun formatToString(context: Context, date: Date, useRelativeDates: Boolean): String {
if (!useRelativeDates) {
val locale = Locale(Locale.getDefault().language)
val dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale)
return dateFormat.format(date)
}

val now = Calendar.getInstance()
val inputDate = Calendar.getInstance().apply { time = date }
val daysDiff = ((now.timeInMillis - inputDate.timeInMillis) / (1000 * 60 * 60 * 24)).toInt()
return when {
daysDiff in -5..-1 -> 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
}
Expand Down Expand Up @@ -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()
}
}

/**
Expand Down Expand Up @@ -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
Expand Down
17 changes: 2 additions & 15 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,7 @@
<string name="core_date_type_next_week" tools:ignore="MissingTranslation">Next Week</string>
<string name="core_date_type_upcoming" tools:ignore="MissingTranslation">Upcoming</string>
<string name="core_date_type_none" tools:ignore="MissingTranslation">None</string>
<string name="core_date_format_today" tools:ignore="MissingTranslation">Today</string>
<string name="core_date_format_tomorrow" tools:ignore="MissingTranslation">Tomorrow</string>
<string name="core_date_format_yesterday" tools:ignore="MissingTranslation">Yesterday</string>
<string name="core_date_format_days_ago" tools:ignore="MissingTranslation">%1$s days ago</string>
<string name="core_date_format_assignment_due_today" tools:ignore="MissingTranslation">Due Today</string>
<string name="core_date_format_assignment_due_tomorrow" tools:ignore="MissingTranslation">Due Tomorrow</string>
<string name="core_date_format_assignment_due_yesterday" tools:ignore="MissingTranslation">Due Yesterday</string>
<plurals name="core_date_format_assignment_due_days_ago" tools:ignore="MissingTranslation">
<item quantity="one">Due %1$d day ago</item>
<item quantity="other">Due %1$d days ago</item>
</plurals>
<plurals name="core_date_format_assignment_due_in" tools:ignore="MissingTranslation">
<item quantity="one">Due in %1$d day</item>
<item quantity="other">Due in %1$d days</item>
</plurals>
<string name="core_date_format_assignment_due" tools:ignore="MissingTranslation">Due %1$s</string>
<plurals name="core_date_items_hidden" tools:ignore="MissingTranslation">
<item quantity="one">%d Item Hidden</item>
<item quantity="other">%d Items Hidden</item>
Expand Down Expand Up @@ -193,4 +179,5 @@
<string name="core_to_sync">To Sync</string>
<string name="core_not_synced">Not Synced</string>
<string name="core_syncing_to_calendar">Syncing to calendar…</string>
<string name="core_next">Next</string>
</resources>
Loading

0 comments on commit 4c1a909

Please sign in to comment.