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 ability to handle course errors #18

Merged
merged 4 commits into from
Aug 1, 2024
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
11 changes: 7 additions & 4 deletions app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
fm: FragmentManager,
courseId: String,
courseTitle: String,
enrollmentMode: String,
) {
replaceFragmentWithBackStack(
fm,
CourseContainerFragment.newInstance(courseId, courseTitle, enrollmentMode)
CourseContainerFragment.newInstance(courseId, courseTitle)
)
}
//endregion
Expand All @@ -164,7 +163,6 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
fm: FragmentManager,
courseId: String,
courseTitle: String,
enrollmentMode: String,
openTab: String,
resumeBlockId: String,
) {
Expand All @@ -173,7 +171,6 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
CourseContainerFragment.newInstance(
courseId,
courseTitle,
enrollmentMode,
openTab,
resumeBlockId
)
Expand Down Expand Up @@ -397,6 +394,12 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, VideoQualityFragment.newInstance(videoQualityType.name))
}

override fun navigateToDiscover(fm: FragmentManager) {
fm.beginTransaction()
.replace(R.id.container, MainFragment.newInstance("", "", "DISCOVER"))
.commit()
}

override fun navigateToWebContent(fm: FragmentManager, title: String, url: String) {
replaceFragmentWithBackStack(
fm,
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ class DeepLinkRouter(
fm = fm,
courseId = courseId,
courseTitle = courseTitle,
enrollmentMode = ""
)
}
}
Expand All @@ -311,7 +310,6 @@ class DeepLinkRouter(
fm = fm,
courseId = courseId,
courseTitle = "",
enrollmentMode = "",
openTab = "VIDEOS"
)
}
Expand All @@ -323,7 +321,6 @@ class DeepLinkRouter(
fm = fm,
courseId = courseId,
courseTitle = "",
enrollmentMode = "",
openTab = "DATES"
)
}
Expand All @@ -335,7 +332,6 @@ class DeepLinkRouter(
fm = fm,
courseId = courseId,
courseTitle = "",
enrollmentMode = "",
openTab = "DISCUSSIONS"
)
}
Expand All @@ -347,7 +343,6 @@ class DeepLinkRouter(
fm = fm,
courseId = courseId,
courseTitle = "",
enrollmentMode = "",
openTab = "MORE"
)
}
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/openedx/app/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.openedx.auth.presentation.sso.MicrosoftAuthHelper
import org.openedx.auth.presentation.sso.OAuthHelper
import org.openedx.core.ImageProcessor
import org.openedx.core.config.Config
import org.openedx.core.data.model.CourseEnrollmentDetails
import org.openedx.core.data.model.CourseEnrollments
import org.openedx.core.data.model.CourseStructureModel
import org.openedx.core.data.storage.CorePreferences
Expand Down Expand Up @@ -97,6 +98,10 @@ val appModule = module {
CourseStructureModel::class.java,
CourseStructureModel.Deserializer(get())
)
.registerTypeAdapter(
CourseEnrollmentDetails::class.java,
CourseEnrollmentDetails.Deserializer(get())
)
.create()
}

Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,11 @@ val screenModule = module {
get()
)
}
viewModel { (courseId: String, courseTitle: String, enrollmentMode: String, resumeBlockId: String) ->
viewModel { (courseId: String, courseTitle: String, resumeBlockId: String) ->
CourseContainerViewModel(
courseId,
courseTitle,
resumeBlockId,
enrollmentMode,
get(),
get(),
get(),
Expand Down
2 changes: 1 addition & 1 deletion core/src/edx/org/openedx/core/ui/theme/Colors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ val light_course_home_header_shade = Color(0xFFBABABA)
val light_course_home_back_btn_background = light_surface
val light_settings_title_content = light_surface
val light_progress_bar_color = light_primary_button_background
val light_progress_bar_background_color = light_secondary_variant
val light_progress_bar_background_color = Color(0xFFCCD4E0)


// Dark theme colors scheme
Expand Down
6 changes: 6 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 @@ -5,6 +5,7 @@ import org.openedx.core.data.model.BlocksCompletionBody
import org.openedx.core.data.model.CourseComponentStatus
import org.openedx.core.data.model.CourseDates
import org.openedx.core.data.model.CourseDatesBannerInfo
import org.openedx.core.data.model.CourseEnrollmentDetails
import org.openedx.core.data.model.CourseEnrollments
import org.openedx.core.data.model.CourseStructureModel
import org.openedx.core.data.model.HandoutsModel
Expand Down Expand Up @@ -76,4 +77,9 @@ interface CourseApi {
@Query("status") status: String? = null,
@Query("requested_fields") fields: List<String> = emptyList()
): CourseEnrollments

@GET("/api/mobile/v1/course_info/{course_id}/enrollment_details")
suspend fun getEnrollmentDetails(
@Path("course_id") courseId: String,
): CourseEnrollmentDetails
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,31 @@ import org.openedx.core.utils.TimeUtils
import org.openedx.core.domain.model.CourseAccessDetails as DomainCourseAccessDetails

data class CourseAccessDetails(
@SerializedName("has_unmet_prerequisites")
val hasUnmetPrerequisites: Boolean,
@SerializedName("is_too_early")
val isTooEarly: Boolean,
@SerializedName("is_staff")
val isStaff: Boolean,
@SerializedName("audit_access_expires")
val auditAccessExpires: String?,
@SerializedName("courseware_access")
var coursewareAccess: CoursewareAccess?,
) {
fun mapToDomain(): DomainCourseAccessDetails =
DomainCourseAccessDetails(
TimeUtils.iso8601ToDate(auditAccessExpires ?: ""),
coursewareAccess?.mapToDomain()
)
fun mapToDomain() = DomainCourseAccessDetails(
hasUnmetPrerequisites = hasUnmetPrerequisites,
isTooEarly = isTooEarly,
isStaff = isStaff,
auditAccessExpires = TimeUtils.iso8601ToDate(auditAccessExpires ?: ""),
coursewareAccess = coursewareAccess?.mapToDomain(),
)

fun mapToRoomEntity(): CourseAccessDetailsDb =
CourseAccessDetailsDb(auditAccessExpires, coursewareAccess?.mapToRoomEntity())
CourseAccessDetailsDb(
hasUnmetPrerequisites = hasUnmetPrerequisites,
isTooEarly = isTooEarly,
isStaff = isStaff,
auditAccessExpires = auditAccessExpires,
coursewareAccess = coursewareAccess?.mapToRoomEntity()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.openedx.core.data.model

import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.annotations.SerializedName
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.extension.takeIfNotEmpty
import java.lang.reflect.Type
import org.openedx.core.domain.model.CourseEnrollmentDetails as DomainCourseEnrollmentDetails

data class CourseEnrollmentDetails(
@SerializedName("id")
val id: String,
@SerializedName("course_updates")
val courseUpdates: String?,
@SerializedName("course_handouts")
val courseHandouts: String?,
@SerializedName("discussion_url")
val discussionUrl: String?,
@SerializedName("course_access_details")
val courseAccessDetails: CourseAccessDetails,
@SerializedName("certificate")
val certificate: Certificate?,
@SerializedName("enrollment_details")
val enrollmentDetails: EnrollmentDetails,
@SerializedName("course_info_overview")
val courseInfoOverview: CourseInfoOverview,
) {
fun mapToDomain(): DomainCourseEnrollmentDetails {
return DomainCourseEnrollmentDetails(
id = id,
courseUpdates = courseUpdates ?: "",
courseHandouts = courseHandouts ?: "",
discussionUrl = discussionUrl ?: "",
courseAccessDetails = courseAccessDetails.mapToDomain(),
certificate = certificate?.mapToDomain(),
enrollmentDetails = enrollmentDetails.mapToDomain(),
courseInfoOverview = courseInfoOverview.mapToDomain(),
)
}

class Deserializer(val corePreferences: CorePreferences) :
JsonDeserializer<CourseEnrollmentDetails> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?,
): CourseEnrollmentDetails {
val courseDetails = Gson().fromJson(json, CourseEnrollmentDetails::class.java)
corePreferences.appConfig.iapConfig.productPrefix?.takeIfNotEmpty()?.let {
courseDetails.courseInfoOverview.courseModes?.forEach { courseModes ->
courseModes.setStoreProductSku(it)
}
}
return courseDetails
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.core.domain.model.iap.ProductInfo
import org.openedx.core.extension.isNotNullOrEmpty
import org.openedx.core.utils.TimeUtils
import org.openedx.core.domain.model.CourseInfoOverview as DomainCourseInfoOverview

data class CourseInfoOverview(
@SerializedName("name")
val name: String,
@SerializedName("number")
val number: String,
@SerializedName("org")
val org: String,
@SerializedName("start")
val start: String?,
@SerializedName("start_display")
val startDisplay: String,
@SerializedName("start_type")
val startType: String,
@SerializedName("end")
val end: String?,
@SerializedName("is_self_paced")
val isSelfPaced: Boolean,
@SerializedName("media")
var media: Media?,
@SerializedName("course_sharing_utm_parameters")
val courseSharingUtmParameters: CourseSharingUtmParameters,
@SerializedName("course_about")
val courseAbout: String,
@SerializedName("course_modes")
val courseModes: List<CourseMode>?,
) {
fun mapToDomain() = DomainCourseInfoOverview(
name = name,
number = number,
org = org,
start = TimeUtils.iso8601ToDate(start ?: ""),
startDisplay = startDisplay,
startType = startType,
end = TimeUtils.iso8601ToDate(end ?: ""),
isSelfPaced = isSelfPaced,
media = media?.mapToDomain(),
courseSharingUtmParameters = courseSharingUtmParameters.mapToDomain(),
courseAbout = courseAbout,
courseModes = courseModes?.map { it.mapToDomain() },
productInfo = courseModes?.find {
it.isVerifiedMode()
}?.takeIf {
it.androidSku.isNotNullOrEmpty() && it.storeSku.isNotNullOrEmpty()
}?.run {
ProductInfo(
courseSku = androidSku!!,
storeSku = storeSku!!,
lmsUSDPrice = minPrice ?: 0.0
)
}
)
}
25 changes: 19 additions & 6 deletions core/src/main/java/org/openedx/core/data/model/CourseMode.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.core.domain.model.EnrollmentMode
import kotlin.math.ceil
import org.openedx.core.domain.model.CourseMode as DomainCourseMode

/**
* Data class representing the mode of a course ("audit, verified etc"), with various attributes
Expand All @@ -10,20 +12,31 @@ import kotlin.math.ceil
data class CourseMode(
@SerializedName("slug")
val slug: String?,

@SerializedName("sku")
val sku: String?,

@SerializedName("android_sku")
val androidSku: String?,

@SerializedName("ios_sku")
val iosSku: String?,
@SerializedName("min_price")
val price: Double?,

val minPrice: Double?,
var storeSku: String?,
) {
fun mapToDomain() = DomainCourseMode(
slug = slug,
sku = sku,
androidSku = androidSku,
iosSku = iosSku,
minPrice = minPrice,
storeSku = storeSku
)

fun isVerifiedMode(): Boolean {
return EnrollmentMode.VERIFIED.toString().equals(slug, ignoreCase = true)
}

fun setStoreProductSku(storeProductPrefix: String) {
val ceilPrice = price
val ceilPrice = minPrice
?.let { ceil(it).toInt() }
?.takeIf { it > 0 }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import org.openedx.core.data.model.room.MediaDb
import org.openedx.core.data.model.room.discovery.ProgressDb
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.domain.model.EnrollmentMode
import org.openedx.core.domain.model.iap.ProductInfo
import org.openedx.core.extension.isNotNullOrEmpty
import org.openedx.core.utils.TimeUtils
import java.lang.reflect.Type

Expand Down Expand Up @@ -73,14 +73,14 @@ data class CourseStructureModel(
progress = progress?.mapToDomain(),
enrollmentDetails = enrollmentDetails.mapToDomain(),
productInfo = courseModes?.find {
EnrollmentMode.VERIFIED.toString().equals(it.slug, ignoreCase = true)
it.isVerifiedMode()
}?.takeIf {
it.androidSku.isNullOrEmpty().not() && it.storeSku.isNullOrEmpty().not()
it.androidSku.isNotNullOrEmpty() && it.storeSku.isNotNullOrEmpty()
}?.run {
ProductInfo(
courseSku = androidSku!!,
storeSku = storeSku!!,
lmsUSDPrice = price ?: 0.0
lmsUSDPrice = minPrice ?: 0.0
)
}
)
Expand Down Expand Up @@ -112,7 +112,7 @@ data class CourseStructureModel(
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
context: JsonDeserializationContext?,
): CourseStructureModel {
val courseStructure = Gson().fromJson(json, CourseStructureModel::class.java)
if (corePreferences.appConfig.iapConfig.productPrefix.isNullOrEmpty().not()) {
Expand Down
Loading
Loading