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: Add support for Course Dates tab #80

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/java/org/openedx/app/AnalyticsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,15 @@ class AnalyticsManager(context: Context) : DashboardAnalytics, AuthAnalytics, Ap
)
}

override fun datesTabClickedEvent(courseId: String, courseName: String) {
logEvent(
Event.DATES_TAB_CLICKED, bundleOf(
Key.COURSE_ID.keyName to courseId,
Key.COURSE_NAME.keyName to courseName
)
)
}

override fun handoutsTabClickedEvent(courseId: String, courseName: String) {
logEvent(
Event.HANDOUTS_TAB_CLICKED, bundleOf(
Expand Down Expand Up @@ -402,6 +411,7 @@ private enum class Event(val eventName: String) {
COURSE_TAB_CLICKED("Course_Outline_Course_tab_Clicked"),
VIDEO_TAB_CLICKED("Course_Outline_Videos_tab_Clicked"),
DISCUSSION_TAB_CLICKED("Course_Outline_Discussion_tab_Clicked"),
DATES_TAB_CLICKED("Course_Outline_Dates_tab_Clicked"),
HANDOUTS_TAB_CLICKED("Course_Outline_Handouts_tab_Clicked"),
DISCUSSION_ALL_POSTS_CLICKED("Discussion_All_Posts_Clicked"),
DISCUSSION_FOLLOWING_CLICKED("Discussion_Following_Clicked"),
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import org.openedx.core.presentation.dialog.selectorbottomsheet.SelectDialogView
import org.openedx.course.data.repository.CourseRepository
import org.openedx.course.domain.interactor.CourseInteractor
import org.openedx.course.presentation.container.CourseContainerViewModel
import org.openedx.course.presentation.dates.CourseDatesViewModel
import org.openedx.course.presentation.detail.CourseDetailsViewModel
import org.openedx.course.presentation.handouts.HandoutsViewModel
import org.openedx.course.presentation.outline.CourseOutlineViewModel
import org.openedx.course.presentation.section.CourseSectionViewModel
import org.openedx.course.presentation.unit.container.CourseUnitContainerViewModel
import org.openedx.course.presentation.unit.video.EncodedVideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoViewModel
import org.openedx.course.presentation.videos.CourseVideoViewModel
import org.openedx.dashboard.data.repository.DashboardRepository
Expand All @@ -41,14 +44,12 @@ import org.openedx.discussion.presentation.topics.DiscussionTopicsViewModel
import org.openedx.profile.data.repository.ProfileRepository
import org.openedx.profile.domain.interactor.ProfileInteractor
import org.openedx.profile.domain.model.Account
import org.openedx.profile.presentation.anothers_account.AnothersProfileViewModel
import org.openedx.profile.presentation.delete.DeleteProfileViewModel
import org.openedx.profile.presentation.edit.EditProfileViewModel
import org.openedx.profile.presentation.profile.ProfileViewModel
import org.openedx.profile.presentation.settings.video.VideoQualityViewModel
import org.openedx.profile.presentation.settings.video.VideoSettingsViewModel
import org.openedx.course.presentation.unit.video.EncodedVideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoUnitViewModel
import org.openedx.profile.presentation.anothers_account.AnothersProfileViewModel
import org.openedx.whatsnew.presentation.whatsnew.WhatsNewViewModel

val screenModule = module {
Expand Down Expand Up @@ -91,6 +92,7 @@ val screenModule = module {
viewModel { (courseId: String) -> VideoViewModel(courseId, get(), get(), get()) }
viewModel { (courseId: String) -> VideoUnitViewModel(courseId, get(), get(), get(), get()) }
viewModel { (courseId: String, blockId: String) -> EncodedVideoUnitViewModel(courseId, blockId, get(), get(), get(), get(), get(), get()) }
viewModel { (courseId: String) -> CourseDatesViewModel(courseId, get(), get(), get()) }
viewModel { (courseId:String, handoutsType: String) -> HandoutsViewModel(courseId, handoutsType, get()) }
viewModel { CourseSearchViewModel(get(), get(), get()) }
viewModel { SelectDialogViewModel(get()) }
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ ext {

window_version = '1.1.0'

extented_spans_version = "1.3.0"

//testing
mockk_version = '1.13.3'
android_arch_version = '2.2.0'
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/org/openedx/core/data/api/CourseApi.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.openedx.core.data.api

import org.openedx.core.data.model.*
import okhttp3.ResponseBody
import org.openedx.core.data.model.*
import retrofit2.http.*

interface CourseApi {
Expand Down Expand Up @@ -65,6 +65,9 @@ interface CourseApi {
blocksCompletionBody: BlocksCompletionBody
)

@GET("/api/course_home/v1/dates/{course_id}")
suspend fun getCourseDates(@Path("course_id") courseId: String): CourseDates

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

Expand Down
28 changes: 28 additions & 0 deletions core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt
farhan-arshad-dev marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName
import java.util.*

data class CourseDateBlock(
@SerializedName("complete")
val complete: Boolean = false,
@SerializedName("date")
val date: String = "", // ISO 8601 compliant format
@SerializedName("assignment_type")
val assignmentType: String? = "",
@SerializedName("date_type")
val dateType: DateType = DateType.NONE,
@SerializedName("description")
val description: String = "",
@SerializedName("learner_has_access")
val learnerHasAccess: Boolean = false,
@SerializedName("link")
val link: String = "",
@SerializedName("link_text")
val linkText: String = "",
@SerializedName("title")
val title: String = "",
// component blockId in-case of navigating inside the app for component available in mobile
@SerializedName("first_component_block_id")
val blockId: String = "",
)
163 changes: 163 additions & 0 deletions core/src/main/java/org/openedx/core/data/model/CourseDates.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.core.presentation.course.CourseDatesBadge
import org.openedx.core.utils.TimeUtils
import java.util.Date
import org.openedx.core.domain.model.CourseDateBlock as DomainCourseDateBlock

data class CourseDates(
@SerializedName("dates_banner_info")
val datesBannerInfo: CourseDatesBannerInfo?,
@SerializedName("course_date_blocks")
val courseDateBlocks: List<CourseDateBlock>,
@SerializedName("missed_deadlines")
val missedDeadlines: Boolean = false,
@SerializedName("missed_gated_content")
val missedGatedContent: Boolean = false,
@SerializedName("learner_is_full_access")
val learnerIsFullAccess: Boolean = false,
@SerializedName("user_timezone")
val userTimezone: String? = "",
@SerializedName("verified_upgrade_link")
val verifiedUpgradeLink: String? = "",
) {
fun mapToDomain(): LinkedHashMap<String, ArrayList<DomainCourseDateBlock>> {
var courseDatesDomain = organiseCourseDatesInBlock()
if (isContainToday().not()) {
// Adding today's date block manually if not present in the date
val todayBlock = DomainCourseDateBlock.getTodayDateBlock()
courseDatesDomain[TimeUtils.formatDate(TimeUtils.FORMAT_DATE, todayBlock.date)] =
arrayListOf(todayBlock)
}
// Sort the map entries date keys wise
courseDatesDomain = LinkedHashMap(courseDatesDomain.toSortedMap(compareBy {
TimeUtils.stringToDate(TimeUtils.FORMAT_DATE, it)
}))
reviseDateBlockBadge(courseDatesDomain)
return courseDatesDomain
}

/**
* Map the date blocks according to dates and stack all the blocks of same date against one key
*/
private fun organiseCourseDatesInBlock(): LinkedHashMap<String, ArrayList<DomainCourseDateBlock>> {
val courseDates =
LinkedHashMap<String, ArrayList<DomainCourseDateBlock>>()
courseDateBlocks.forEach { item ->
val key =
TimeUtils.formatDate(TimeUtils.FORMAT_DATE, TimeUtils.iso8601ToDate(item.date))
val dateBlock = DomainCourseDateBlock(
title = item.title,
description = item.description,
link = item.link,
blockId = item.blockId,
date = TimeUtils.iso8601ToDate(item.date),
complete = item.complete,
learnerHasAccess = item.learnerHasAccess,
dateType = item.dateType,
dateBlockBadge = CourseDatesBadge.BLANK
)
if (courseDates.containsKey(key)) {
(courseDates[key] as ArrayList).add(dateBlock)
} else {
courseDates[key] = arrayListOf(dateBlock)
}
}
return courseDates
}

/**
* Utility method to check that list contains today's date block or not.
*/
private fun isContainToday(): Boolean {
val today = Date()
return courseDateBlocks.any { blockDate ->
TimeUtils.iso8601ToDate(blockDate.date) == today
}
}

/**
* Set the Date Block Badge based on the date block data
*/
private fun reviseDateBlockBadge(courseDatesDomain: LinkedHashMap<String, ArrayList<DomainCourseDateBlock>>) {
var dueNextCount = 0
courseDatesDomain.keys.forEach { key ->
courseDatesDomain[key]?.forEach { item ->
var dateBlockTag: CourseDatesBadge = getDateTypeBadge(item)
//Setting Due Next only for first occurrence
if (dateBlockTag == CourseDatesBadge.DUE_NEXT) {
if (dueNextCount == 0)
dueNextCount += 1
else
dateBlockTag = CourseDatesBadge.BLANK
}
item.dateBlockBadge = dateBlockTag
}
}
}

/**
* Return Pill/Badge type of date block based on data
*/
private fun getDateTypeBadge(item: DomainCourseDateBlock): CourseDatesBadge {
val dateBlockTag: CourseDatesBadge
val currentDate = Date()
val componentDate: Date = item.date ?: return CourseDatesBadge.BLANK
when (item.dateType) {
DateType.TODAY_DATE -> {
dateBlockTag = CourseDatesBadge.TODAY
}

DateType.COURSE_START_DATE,
DateType.COURSE_END_DATE -> {
dateBlockTag = CourseDatesBadge.BLANK
}

DateType.ASSIGNMENT_DUE_DATE -> {
when {
item.complete -> {
dateBlockTag = CourseDatesBadge.COMPLETED
}

item.learnerHasAccess -> {
dateBlockTag = when {
item.link.isEmpty() -> {
CourseDatesBadge.NOT_YET_RELEASED
}

TimeUtils.isDueDate(currentDate, componentDate) -> {
CourseDatesBadge.DUE_NEXT
}

TimeUtils.isDatePassed(currentDate, componentDate) -> {
CourseDatesBadge.PAST_DUE
}

else -> {
CourseDatesBadge.BLANK
}
}
}

else -> {
dateBlockTag = CourseDatesBadge.VERIFIED_ONLY
}
}
}

DateType.COURSE_EXPIRED_DATE -> {
dateBlockTag = CourseDatesBadge.COURSE_EXPIRED_DATE
}

else -> {
// dateBlockTag is BLANK for all other cases
// DateTypes.CERTIFICATE_AVAILABLE_DATE,
// DateTypes.VERIFIED_UPGRADE_DEADLINE,
// DateTypes.VERIFICATION_DEADLINE_DATE
dateBlockTag = CourseDatesBadge.BLANK
}
}
return dateBlockTag
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName

data class CourseDatesBannerInfo(
@SerializedName("missed_deadlines")
val missedDeadlines: Boolean = false,
@SerializedName("missed_gated_content")
val missedGatedContent: Boolean = false,
@SerializedName("verified_upgrade_link")
val verifiedUpgradeLink: String = "",
@SerializedName("content_type_gating_enabled")
val contentTypeGatingEnabled: Boolean = false,
) {
fun getCourseBannerType(): CourseBannerType = when {
farhan-arshad-dev marked this conversation as resolved.
Show resolved Hide resolved
upgradeToGraded() -> CourseBannerType.UPGRADE_TO_GRADED
upgradeToReset() -> CourseBannerType.UPGRADE_TO_RESET
resetDates() -> CourseBannerType.RESET_DATES
showBannerInfo() -> CourseBannerType.INFO_BANNER
else -> CourseBannerType.BLANK
}

private fun showBannerInfo(): Boolean = missedDeadlines.not()

private fun upgradeToGraded(): Boolean = contentTypeGatingEnabled && missedDeadlines.not()

private fun upgradeToReset(): Boolean =
upgradeToGraded().not() && missedDeadlines && missedGatedContent

private fun resetDates(): Boolean =
upgradeToGraded().not() && missedDeadlines && missedGatedContent.not()
}

enum class CourseBannerType {
BLANK, INFO_BANNER, UPGRADE_TO_GRADED, UPGRADE_TO_RESET, RESET_DATES;
}
31 changes: 31 additions & 0 deletions core/src/main/java/org/openedx/core/data/model/DateType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName

enum class DateType {
@SerializedName("todays-date")
TODAY_DATE,

@SerializedName("course-start-date")
COURSE_START_DATE,

@SerializedName("course-end-date")
COURSE_END_DATE,

@SerializedName("course-expired-date")
COURSE_EXPIRED_DATE,

@SerializedName("assignment-due-date")
ASSIGNMENT_DUE_DATE,

@SerializedName("certificate-available-date")
CERTIFICATE_AVAILABLE_DATE,

@SerializedName("verified-upgrade-deadline")
VERIFIED_UPGRADE_DEADLINE,

@SerializedName("verification-deadline-date")
VERIFICATION_DEADLINE_DATE,
farhan-arshad-dev marked this conversation as resolved.
Show resolved Hide resolved

NONE,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.openedx.core.domain.model

import org.openedx.core.data.model.DateType
import org.openedx.core.presentation.course.CourseDatesBadge
import java.util.Date

data class CourseDateBlock(
val title: String = "",
val description: String = "",
val link: String = "",
val blockId: String = "",
val learnerHasAccess: Boolean = false,
val complete: Boolean = false,
val date: Date?,
val dateType: DateType = DateType.NONE,
var dateBlockBadge: CourseDatesBadge = CourseDatesBadge.BLANK,
) {
companion object {
fun getTodayDateBlock() =
CourseDateBlock(
date = Date(),
dateType = DateType.TODAY_DATE,
dateBlockBadge = CourseDatesBadge.TODAY
)
}
}
Loading