Skip to content
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
25 changes: 21 additions & 4 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,23 @@ val screenModule = module {
viewModel { (qualityType: String) -> VideoQualityViewModel(qualityType, get(), get(), get()) }
viewModel { DeleteProfileViewModel(get(), get(), get(), get(), get()) }
viewModel { (username: String) -> AnothersProfileViewModel(get(), get(), username) }
viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel {
SettingsViewModel(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
viewModel { ManageAccountViewModel(get(), get(), get(), get(), get()) }

single { CourseRepository(get(), get(), get(), get()) }
single { CourseRepository(get(), get(), get(), get(), get()) }
factory { CourseInteractor(get()) }
viewModel { (pathId: String, infoType: String) ->
CourseInfoViewModel(
Expand Down Expand Up @@ -279,8 +292,10 @@ val screenModule = module {
get(),
)
}
viewModel { (enrollmentMode: String) ->
viewModel { (courseId: String, courseTitle: String, enrollmentMode: String) ->
CourseDatesViewModel(
courseId,
courseTitle,
enrollmentMode,
get(),
get(),
Expand All @@ -305,8 +320,10 @@ val screenModule = module {

single { DiscussionRepository(get(), get(), get()) }
factory { DiscussionInteractor(get()) }
viewModel {
viewModel { (courseId: String, courseTitle: String) ->
DiscussionTopicsViewModel(
courseId,
courseTitle,
get(),
get(),
get(),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ class CourseNotifier {
suspend fun send(event: CalendarSyncEvent) = channel.emit(event)
suspend fun send(event: CourseDatesShifted) = channel.emit(event)
suspend fun send(event: CourseLoading) = channel.emit(event)
suspend fun send(event: CourseDataReady) = channel.emit(event)
suspend fun send(event: CourseRefresh) = channel.emit(event)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import org.openedx.core.ApiConstants
import org.openedx.core.data.api.CourseApi
import org.openedx.core.data.model.BlocksCompletionBody
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.*
import org.openedx.core.domain.model.CourseComponentStatus
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.exception.NoCachedDataException
import org.openedx.core.module.db.DownloadDao
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.course.data.storage.CourseDao

class CourseRepository(
private val api: CourseApi,
private val courseDao: CourseDao,
private val downloadDao: DownloadDao,
private val preferencesManager: CorePreferences,
private val networkConnection: NetworkConnection,
) {
private var courseStructure: CourseStructure? = null
private var courseStructure = mutableMapOf<String, CourseStructure>()

suspend fun removeDownloadModel(id: String) {
downloadDao.removeDownloadModel(id)
Expand All @@ -26,35 +29,33 @@ class CourseRepository(
list.map { it.mapToDomain() }
}

suspend fun preloadCourseStructure(courseId: String) {
val response = api.getCourseStructure(
"stale-if-error=0",
"v3",
preferencesManager.user?.username,
courseId
)
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
courseStructure = null
courseStructure = response.mapToDomain()
fun hasCourses(courseId: String): Boolean {
return courseStructure[courseId] != null
}

suspend fun preloadCourseStructureFromCache(courseId: String) {
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
courseStructure = null
if (cachedCourseStructure != null) {
courseStructure = cachedCourseStructure.mapToDomain()
} else {
throw NoCachedDataException()
}
}
suspend fun getCourseStructure(courseId: String, isNeedRefresh: Boolean): CourseStructure {
if (!isNeedRefresh) courseStructure[courseId]?.let { return it }

if (networkConnection.isOnline()) {
val response = api.getCourseStructure(
"stale-if-error=0",
"v3",
preferencesManager.user?.username,
courseId
)
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
courseStructure[courseId] = response.mapToDomain()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the extra line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@Throws(IllegalStateException::class)
fun getCourseStructureFromCache(): CourseStructure {
if (courseStructure != null) {
return courseStructure!!
} else {
throw IllegalStateException("Course structure is empty")
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
if (cachedCourseStructure != null) {
courseStructure[courseId] = cachedCourseStructure.mapToDomain()
} else {
throw NoCachedDataException()
}
}

return courseStructure[courseId]!!
}

suspend fun getCourseStatus(courseId: String): CourseComponentStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ class CourseInteractor(
private val repository: CourseRepository
) {

suspend fun preloadCourseStructure(courseId: String) =
repository.preloadCourseStructure(courseId)

suspend fun preloadCourseStructureFromCache(courseId: String) =
repository.preloadCourseStructureFromCache(courseId)

@Throws(IllegalStateException::class)
fun getCourseStructureFromCache() = repository.getCourseStructureFromCache()
suspend fun getCourseStructure(
courseId: String,
isNeedRefresh: Boolean = false
): CourseStructure {
return repository.getCourseStructure(courseId, isNeedRefresh)
}

@Throws(IllegalStateException::class)
fun getCourseStructureForVideos(): CourseStructure {
val courseStructure = repository.getCourseStructureFromCache()
suspend fun getCourseStructureForVideos(
courseId: String,
isNeedRefresh: Boolean = false
): CourseStructure {
val courseStructure = repository.getCourseStructure(courseId, isNeedRefresh)
val blocks = courseStructure.blockData
val videoBlocks = blocks.filter { it.type == BlockType.VIDEO }
val resultBlocks = ArrayList<Block>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand Down Expand Up @@ -294,6 +295,8 @@ fun CourseDashboard(
val refreshing by viewModel.refreshing.collectAsState(true)
val courseImage by viewModel.courseImage.collectAsState()
val uiMessage by viewModel.uiMessage.collectAsState(null)
val dataReady = viewModel.dataReady.observeAsState()

val pagerState = rememberPagerState(pageCount = { CourseContainerTab.entries.size })
val tabState = rememberLazyListState()
val snackState = remember { SnackbarHostState() }
Expand Down Expand Up @@ -351,15 +354,17 @@ fun CourseDashboard(
fragmentManager.popBackStack()
},
bodyContent = {
DashboardPager(
windowSize = windowSize,
viewModel = viewModel,
pagerState = pagerState,
isNavigationEnabled = isNavigationEnabled,
isResumed = isResumed,
fragmentManager = fragmentManager,
bundle = bundle
)
if (dataReady.value == true) {
DashboardPager(
windowSize = windowSize,
viewModel = viewModel,
pagerState = pagerState,
isNavigationEnabled = isNavigationEnabled,
isResumed = isResumed,
fragmentManager = fragmentManager,
bundle = bundle
)
}
}
)
PullRefreshIndicator(
Expand Down Expand Up @@ -462,6 +467,8 @@ fun DashboardPager(
courseDatesViewModel = koinViewModel(
parameters = {
parametersOf(
bundle.getString(CourseContainerFragment.ARG_COURSE_ID, ""),
bundle.getString(CourseContainerFragment.ARG_TITLE, ""),
bundle.getString(CourseContainerFragment.ARG_ENROLLMENT_MODE, "")
)
}
Expand All @@ -478,6 +485,14 @@ fun DashboardPager(

CourseContainerTab.DISCUSSIONS -> {
DiscussionTopicsScreen(
discussionTopicsViewModel = koinViewModel(
parameters = {
parametersOf(
bundle.getString(CourseContainerFragment.ARG_COURSE_ID, ""),
bundle.getString(CourseContainerFragment.ARG_TITLE, ""),
)
}
),
windowSize = windowSize,
fragmentManager = fragmentManager
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
import org.openedx.core.system.notifier.CourseCompletionSet
import org.openedx.core.system.notifier.CourseDataReady
import org.openedx.core.system.notifier.CourseDatesShifted
import org.openedx.core.system.notifier.CourseLoading
import org.openedx.core.system.notifier.CourseNotifier
Expand Down Expand Up @@ -169,12 +168,7 @@ class CourseContainerViewModel(
_showProgress.value = true
viewModelScope.launch {
try {
if (networkConnection.isOnline()) {
interactor.preloadCourseStructure(courseId)
} else {
interactor.preloadCourseStructureFromCache(courseId)
}
val courseStructure = interactor.getCourseStructureFromCache()
val courseStructure = interactor.getCourseStructure(courseId)
courseName = courseStructure.name
_organization = courseStructure.org
_isSelfPaced = courseStructure.isSelfPaced
Expand All @@ -183,7 +177,6 @@ class CourseContainerViewModel(
val isReady = start < Date()
if (isReady) {
_isNavigationEnabled.value = true
courseNotifier.send(CourseDataReady(courseStructure))
}
isReady
}
Expand Down Expand Up @@ -248,7 +241,7 @@ class CourseContainerViewModel(
fun updateData() {
viewModelScope.launch {
try {
interactor.preloadCourseStructure(courseId)
interactor.getCourseStructure(courseId, isNeedRefresh = true)
} catch (e: Exception) {
if (e.isInternetError()) {
_errorMessage.value =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.CourseBannerType
import org.openedx.core.domain.model.CourseDateBlock
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.extension.getSequentialBlocks
import org.openedx.core.extension.getVerticalBlocks
import org.openedx.core.extension.isInternetError
import org.openedx.core.presentation.course.CourseContainerTab
import org.openedx.core.system.ResourceManager
import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
import org.openedx.core.system.notifier.CourseDataReady
import org.openedx.core.system.notifier.CourseDatesShifted
import org.openedx.core.system.notifier.CourseLoading
import org.openedx.core.system.notifier.CourseNotifier
Expand All @@ -41,6 +41,8 @@ import org.openedx.course.presentation.calendarsync.CalendarSyncUIState
import org.openedx.core.R as CoreR

class CourseDatesViewModel(
val courseId: String,
courseTitle: String,
private val enrollmentMode: String,
private val courseNotifier: CourseNotifier,
private val interactor: CourseInteractor,
Expand All @@ -51,8 +53,6 @@ class CourseDatesViewModel(
private val config: Config,
) : BaseViewModel() {

var courseId = ""
var courseName = ""
var isSelfPaced = true

private val _uiState = MutableLiveData<DatesUIState>(DatesUIState.Loading)
Expand All @@ -66,14 +66,15 @@ class CourseDatesViewModel(
private val _calendarSyncUIState = MutableStateFlow(
CalendarSyncUIState(
isCalendarSyncEnabled = isCalendarSyncEnabled(),
calendarTitle = calendarManager.getCourseCalendarTitle(courseName),
calendarTitle = calendarManager.getCourseCalendarTitle(courseTitle),
isSynced = false,
)
)
val calendarSyncUIState: StateFlow<CalendarSyncUIState> =
_calendarSyncUIState.asStateFlow()

private var courseBannerType: CourseBannerType = CourseBannerType.BLANK
private var courseStructure: CourseStructure? = null

val isCourseExpandableSectionsEnabled get() = config.isCourseNestedListEnabled()

Expand All @@ -90,22 +91,19 @@ class CourseDatesViewModel(
loadingCourseDatesInternal()
}
}

is CourseDataReady -> {
courseId = event.courseStructure.id
courseName = event.courseStructure.name
isSelfPaced = event.courseStructure.isSelfPaced
loadingCourseDatesInternal()
updateAndFetchCalendarSyncState()
}
}
}
}

loadingCourseDatesInternal()
updateAndFetchCalendarSyncState()
}

private fun loadingCourseDatesInternal() {
viewModelScope.launch {
try {
courseStructure = interactor.getCourseStructure(courseId = courseId)
isSelfPaced = courseStructure?.isSelfPaced ?: false
val datesResponse = interactor.getCourseDates(courseId = courseId)
if (datesResponse.datesSection.isEmpty()) {
_uiState.value = DatesUIState.Empty
Expand Down Expand Up @@ -146,18 +144,17 @@ class CourseDatesViewModel(

fun getVerticalBlock(blockId: String): Block? {
return try {
val courseStructure = interactor.getCourseStructureFromCache()
courseStructure.blockData.getVerticalBlocks().find { it.descendants.contains(blockId) }
courseStructure?.blockData?.getVerticalBlocks()
?.find { it.descendants.contains(blockId) }
} catch (e: Exception) {
null
}
}

fun getSequentialBlock(blockId: String): Block? {
return try {
val courseStructure = interactor.getCourseStructureFromCache()
courseStructure.blockData.getSequentialBlocks()
.find { it.descendants.contains(blockId) }
courseStructure?.blockData?.getSequentialBlocks()
?.find { it.descendants.contains(blockId) }
} catch (e: Exception) {
null
}
Expand Down
Loading