@@ -6,9 +6,7 @@ import android.os.Build
66import androidx.lifecycle.LiveData
77import androidx.lifecycle.MutableLiveData
88import androidx.lifecycle.viewModelScope
9- import kotlinx.coroutines.SupervisorJob
109import kotlinx.coroutines.async
11- import kotlinx.coroutines.awaitAll
1210import kotlinx.coroutines.delay
1311import kotlinx.coroutines.flow.MutableSharedFlow
1412import kotlinx.coroutines.flow.MutableStateFlow
@@ -18,11 +16,13 @@ import kotlinx.coroutines.flow.asSharedFlow
1816import kotlinx.coroutines.flow.asStateFlow
1917import kotlinx.coroutines.flow.update
2018import kotlinx.coroutines.launch
19+ import kotlinx.coroutines.supervisorScope
2120import org.openedx.core.config.Config
2221import org.openedx.core.data.storage.CorePreferences
2322import org.openedx.core.domain.model.CourseAccessError
2423import org.openedx.core.domain.model.CourseDatesCalendarSync
2524import org.openedx.core.domain.model.CourseEnrollmentDetails
25+ import org.openedx.core.domain.model.CourseStructure
2626import org.openedx.core.exception.NoCachedDataException
2727import org.openedx.core.extension.isFalse
2828import org.openedx.core.extension.isTrue
@@ -163,62 +163,113 @@ class CourseContainerViewModel(
163163
164164 fun fetchCourseDetails () {
165165 courseDashboardViewed()
166- if ( _dataReady .value != null ) {
167- return
168- }
166+
167+ // If data is already loaded, do nothing
168+ if ( _dataReady .value != null ) return
169169
170170 _showProgress .value = true
171+
171172 viewModelScope.launch {
172173 try {
173- val deferredCourse = async(SupervisorJob ()) {
174- interactor.getCourseStructure(courseId, isNeedRefresh = true )
175- }
176- val deferredEnrollment = async(SupervisorJob ()) {
177- interactor.getEnrollmentDetails(courseId)
178- }
179- val (_, enrollment) = awaitAll(deferredCourse, deferredEnrollment)
180- _courseDetails = enrollment as ? CourseEnrollmentDetails
174+ val (courseStructure, courseEnrollmentDetails) = fetchCourseData(courseId)
181175 _showProgress .value = false
182- _courseDetails ?.let { courseDetails ->
183- courseName = courseDetails.courseInfoOverview.name
184- loadCourseImage(courseDetails.courseInfoOverview.media?.image?.large)
185- if (courseDetails.hasAccess.isFalse()) {
186- _dataReady .value = false
187- if (courseDetails.isAuditAccessExpired) {
188- _courseAccessStatus .value =
189- CourseAccessError .AUDIT_EXPIRED_NOT_UPGRADABLE
190- } else if (courseDetails.courseInfoOverview.isStarted.not ()) {
191- _courseAccessStatus .value = CourseAccessError .NOT_YET_STARTED
192- } else {
193- _courseAccessStatus .value = CourseAccessError .UNKNOWN
194- }
195- } else {
196- _courseAccessStatus .value = CourseAccessError .NONE
197- _isNavigationEnabled .value = true
198- _calendarSyncUIState .update { state ->
199- state.copy(isCalendarSyncEnabled = isCalendarSyncEnabled())
200- }
201- if (resumeBlockId.isNotEmpty()) {
202- delay(500L )
203- courseNotifier.send(CourseOpenBlock (resumeBlockId))
204- }
205- _dataReady .value = true
176+ when {
177+ courseEnrollmentDetails != null -> {
178+ handleCourseEnrollment(courseEnrollmentDetails)
179+ }
180+
181+ courseStructure != null -> {
182+ handleCourseStructureOnly(courseStructure)
183+ }
184+
185+ else -> {
186+ _courseAccessStatus .value = CourseAccessError .UNKNOWN
206187 }
207- } ? : run {
208- _courseAccessStatus .value = CourseAccessError .UNKNOWN
209188 }
210189 } catch (e: Exception ) {
211190 e.printStackTrace()
212- if (isNetworkRelatedError(e)) {
213- _errorMessage .value = resourceManager.getString(CoreR .string.core_error_no_connection)
214- } else {
215- _courseAccessStatus .value = CourseAccessError .UNKNOWN
216- }
191+ handleFetchError(e)
217192 _showProgress .value = false
218193 }
219194 }
220195 }
221196
197+ private suspend fun fetchCourseData (
198+ courseId : String
199+ ): Pair <CourseStructure ?, CourseEnrollmentDetails ?> = supervisorScope {
200+ val deferredCourse = async {
201+ runCatching {
202+ interactor.getCourseStructure(courseId, isNeedRefresh = true )
203+ }.getOrNull()
204+ }
205+ val deferredEnrollment = async {
206+ runCatching {
207+ interactor.getEnrollmentDetails(courseId)
208+ }.getOrNull()
209+ }
210+
211+ Pair (deferredCourse.await(), deferredEnrollment.await())
212+ }
213+
214+ /* *
215+ * Handles the scenario where [CourseEnrollmentDetails] is successfully fetched.
216+ */
217+ private fun handleCourseEnrollment (courseDetails : CourseEnrollmentDetails ) {
218+ _courseDetails = courseDetails
219+ courseName = courseDetails.courseInfoOverview.name
220+ loadCourseImage(courseDetails.courseInfoOverview.media?.image?.large)
221+
222+ if (courseDetails.hasAccess.isFalse()) {
223+ _dataReady .value = false
224+ _courseAccessStatus .value = when {
225+ courseDetails.isAuditAccessExpired -> CourseAccessError .AUDIT_EXPIRED_NOT_UPGRADABLE
226+ courseDetails.courseInfoOverview.isStarted.not () -> CourseAccessError .NOT_YET_STARTED
227+ else -> CourseAccessError .UNKNOWN
228+ }
229+ } else {
230+ _courseAccessStatus .value = CourseAccessError .NONE
231+ _isNavigationEnabled .value = true
232+ _calendarSyncUIState .update { state ->
233+ state.copy(isCalendarSyncEnabled = isCalendarSyncEnabled())
234+ }
235+ if (resumeBlockId.isNotEmpty()) {
236+ // Small delay before sending block open event
237+ viewModelScope.launch {
238+ delay(500L )
239+ courseNotifier.send(CourseOpenBlock (resumeBlockId))
240+ }
241+ }
242+ _dataReady .value = true
243+ }
244+ }
245+
246+ /* *
247+ * Handles the scenario where we only have [CourseStructure] but no enrollment details.
248+ */
249+ private fun handleCourseStructureOnly (courseStructure : CourseStructure ) {
250+ loadCourseImage(courseStructure.media?.image?.large)
251+ _courseAccessStatus .value = CourseAccessError .NONE
252+ _isNavigationEnabled .value = true
253+ _calendarSyncUIState .update { state ->
254+ state.copy(isCalendarSyncEnabled = isCalendarSyncEnabled())
255+ }
256+ if (resumeBlockId.isNotEmpty()) {
257+ viewModelScope.launch {
258+ delay(500L )
259+ courseNotifier.send(CourseOpenBlock (resumeBlockId))
260+ }
261+ }
262+ _dataReady .value = true
263+ }
264+
265+ private fun handleFetchError (e : Exception ) {
266+ if (isNetworkRelatedError(e)) {
267+ _errorMessage .value = resourceManager.getString(CoreR .string.core_error_no_connection)
268+ } else {
269+ _courseAccessStatus .value = CourseAccessError .UNKNOWN
270+ }
271+ }
272+
222273 private fun isNetworkRelatedError (e : Exception ): Boolean {
223274 return e.isInternetError() || e is NoCachedDataException
224275 }
0 commit comments