diff --git a/core/src/main/java/org/openedx/core/module/DownloadWorkerController.kt b/core/src/main/java/org/openedx/core/module/DownloadWorkerController.kt index e440cfcc5..39612ae10 100644 --- a/core/src/main/java/org/openedx/core/module/DownloadWorkerController.kt +++ b/core/src/main/java/org/openedx/core/module/DownloadWorkerController.kt @@ -4,7 +4,6 @@ import android.content.Context import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo import androidx.work.WorkManager -import com.google.common.util.concurrent.ListenableFuture import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -14,7 +13,6 @@ import org.openedx.core.module.db.DownloadModelEntity import org.openedx.core.module.db.DownloadedState import org.openedx.core.module.download.FileDownloader import java.io.File -import java.util.concurrent.ExecutionException class DownloadWorkerController( context: Context, @@ -23,7 +21,6 @@ class DownloadWorkerController( ) { private val workManager = WorkManager.getInstance(context) - private var downloadTaskList = listOf() init { @@ -46,16 +43,15 @@ class DownloadWorkerController( } private suspend fun updateList() { - downloadTaskList = - downloadDao.getAllDataFlow().first().map { it.mapToDomain() }.filter { + downloadTaskList = downloadDao.getAllDataFlow().first() + .map { it.mapToDomain() } + .filter { it.downloadedState == DownloadedState.WAITING || it.downloadedState == DownloadedState.DOWNLOADING } } suspend fun saveModels(downloadModels: List) { - downloadDao.insertDownloadModel( - downloadModels.map { DownloadModelEntity.createFrom(it) } - ) + downloadDao.insertDownloadModel(downloadModels.map { DownloadModelEntity.createFrom(it) }) } suspend fun removeModel(id: String) { @@ -69,11 +65,9 @@ class DownloadWorkerController( downloadModels.forEach { downloadModel -> removeIds.add(downloadModel.id) - if (downloadModel.downloadedState == DownloadedState.DOWNLOADING) { hasDownloading = true } - try { File(downloadModel.path).delete() } catch (e: Exception) { @@ -97,19 +91,14 @@ class DownloadWorkerController( workManager.cancelAllWorkByTag(DownloadWorker.WORKER_TAG) } - private fun isWorkScheduled(tag: String): Boolean { - val statuses: ListenableFuture> = workManager.getWorkInfosByTag(tag) + val statuses = workManager.getWorkInfosByTag(tag) return try { - val workInfoList: List = statuses.get() - val workInfo = workInfoList.find { - (it.state == WorkInfo.State.RUNNING) or (it.state == WorkInfo.State.ENQUEUED) + val workInfo = statuses.get().find { + it.state == WorkInfo.State.RUNNING || it.state == WorkInfo.State.ENQUEUED } workInfo != null - } catch (e: ExecutionException) { - e.printStackTrace() - false - } catch (e: InterruptedException) { + } catch (e: Exception) { e.printStackTrace() false } diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt index 64a95d2d8..5a85ba191 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt @@ -27,91 +27,44 @@ class DownloadDialogManager( } private val uiState = MutableSharedFlow() + private val coroutineScope = CoroutineScope(Dispatchers.IO) init { - CoroutineScope(Dispatchers.IO).launch { - uiState.collect { uiState -> - when { - uiState.isDownloadFailed -> { - val dialog = DownloadErrorDialogFragment.newInstance( - dialogType = DownloadErrorDialogType.DOWNLOAD_FAILED, - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadErrorDialogFragment.DIALOG_TAG - ) - } - - uiState.isAllBlocksDownloaded -> { - val dialog = DownloadConfirmDialogFragment.newInstance( - dialogType = DownloadConfirmDialogType.REMOVE, - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadConfirmDialogFragment.DIALOG_TAG - ) - } - - !networkConnection.isOnline() -> { - val dialog = DownloadErrorDialogFragment.newInstance( - dialogType = DownloadErrorDialogType.NO_CONNECTION, - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadErrorDialogFragment.DIALOG_TAG - ) - } - - StorageManager.getFreeStorage() < uiState.sizeSum * DOWNLOAD_SIZE_FACTOR -> { - val dialog = DownloadStorageErrorDialogFragment.newInstance( - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadStorageErrorDialogFragment.DIALOG_TAG - ) - } - - corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> { - val dialog = DownloadErrorDialogFragment.newInstance( - dialogType = DownloadErrorDialogType.WIFI_REQUIRED, - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadErrorDialogFragment.DIALOG_TAG - ) - } - - !corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> { - val dialog = DownloadConfirmDialogFragment.newInstance( - dialogType = DownloadConfirmDialogType.DOWNLOAD_ON_CELLULAR, - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadConfirmDialogFragment.DIALOG_TAG - ) - } - - uiState.sizeSum >= MAX_CELLULAR_SIZE -> { - val dialog = DownloadConfirmDialogFragment.newInstance( - dialogType = DownloadConfirmDialogType.CONFIRM, - uiState = uiState - ) - dialog.show( - uiState.fragmentManager, - DownloadConfirmDialogFragment.DIALOG_TAG - ) - } - - else -> { - uiState.saveDownloadModels() - } + coroutineScope.launch { + uiState.collect { state -> + val dialog = when { + state.isDownloadFailed -> DownloadErrorDialogFragment.newInstance( + dialogType = DownloadErrorDialogType.DOWNLOAD_FAILED, uiState = state + ) + + state.isAllBlocksDownloaded -> DownloadConfirmDialogFragment.newInstance( + dialogType = DownloadConfirmDialogType.REMOVE, uiState = state + ) + + !networkConnection.isOnline() -> DownloadErrorDialogFragment.newInstance( + dialogType = DownloadErrorDialogType.NO_CONNECTION, uiState = state + ) + + StorageManager.getFreeStorage() < state.sizeSum * DOWNLOAD_SIZE_FACTOR -> DownloadStorageErrorDialogFragment.newInstance( + uiState = state + ) + + corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> DownloadErrorDialogFragment.newInstance( + dialogType = DownloadErrorDialogType.WIFI_REQUIRED, uiState = state + ) + + !corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> DownloadConfirmDialogFragment.newInstance( + dialogType = DownloadConfirmDialogType.DOWNLOAD_ON_CELLULAR, uiState = state + ) + + state.sizeSum >= MAX_CELLULAR_SIZE -> DownloadConfirmDialogFragment.newInstance( + dialogType = DownloadConfirmDialogType.CONFIRM, uiState = state + ) + + else -> null } + + dialog?.show(state.fragmentManager, dialog::class.java.simpleName) ?: state.saveDownloadModels() } } } @@ -141,7 +94,7 @@ class DownloadDialogManager( fragmentManager: FragmentManager, removeDownloadModels: () -> Unit, ) { - CoroutineScope(Dispatchers.IO).launch { + coroutineScope.launch { uiState.emit( DownloadDialogUIState( downloadDialogItems = listOf(downloadDialogItem), @@ -161,39 +114,44 @@ class DownloadDialogManager( fragmentManager: FragmentManager, ) { createDownloadItems( - downloadModel = downloadModel, + downloadModels = downloadModel, fragmentManager = fragmentManager, ) } private fun createDownloadItems( - downloadModel: List, + downloadModels: List, fragmentManager: FragmentManager, ) { - CoroutineScope(Dispatchers.IO).launch { - val courseIds = downloadModel.map { it.courseId }.distinct() - val blockIds = downloadModel.map { it.id } + coroutineScope.launch { + val courseIds = downloadModels.map { it.courseId }.distinct() + val blockIds = downloadModels.map { it.id } val notDownloadedSubSections = mutableListOf() val allDownloadDialogItems = mutableListOf() + courseIds.forEach { courseId -> val courseStructure = interactor.getCourseStructureFromCache(courseId) val allSubSectionBlocks = courseStructure.blockData.filter { it.type == BlockType.SEQUENTIAL } - allSubSectionBlocks.forEach { subSectionsBlock -> - val verticalBlocks = courseStructure.blockData.filter { it.id in subSectionsBlock.descendants } + + allSubSectionBlocks.forEach { subSectionBlock -> + val verticalBlocks = courseStructure.blockData.filter { it.id in subSectionBlock.descendants } val blocks = courseStructure.blockData.filter { it.id in verticalBlocks.flatMap { it.descendants } && it.id in blockIds } - val size = blocks.sumOf { getFileSize(it) } - if (blocks.isNotEmpty()) notDownloadedSubSections.add(subSectionsBlock) - if (size > 0) { - val downloadDialogItem = DownloadDialogItem( - title = subSectionsBlock.displayName, - size = size + val totalSize = blocks.sumOf { getFileSize(it) } + + if (blocks.isNotEmpty()) notDownloadedSubSections.add(subSectionBlock) + if (totalSize > 0) { + allDownloadDialogItems.add( + DownloadDialogItem( + title = subSectionBlock.displayName, + size = totalSize + ) ) - allDownloadDialogItems.add(downloadDialogItem) } } } + uiState.emit( DownloadDialogUIState( downloadDialogItems = allDownloadDialogItems, @@ -203,8 +161,8 @@ class DownloadDialogManager( fragmentManager = fragmentManager, removeDownloadModels = {}, saveDownloadModels = { - CoroutineScope(Dispatchers.IO).launch { - workerController.saveModels(downloadModel) + coroutineScope.launch { + workerController.saveModels(downloadModels) } } ) @@ -221,12 +179,12 @@ class DownloadDialogManager( removeDownloadModels: (blockId: String) -> Unit, saveDownloadModels: (blockId: String) -> Unit, ) { - CoroutineScope(Dispatchers.IO).launch { + coroutineScope.launch { val courseStructure = interactor.getCourseStructure(courseId, false) val downloadModelIds = interactor.getAllDownloadModels().map { it.id } - val downloadDialogItems = subSectionsBlocks.mapNotNull { subSectionsBlock -> - val verticalBlocks = courseStructure.blockData.filter { it.id in subSectionsBlock.descendants } + val downloadDialogItems = subSectionsBlocks.mapNotNull { subSectionBlock -> + val verticalBlocks = courseStructure.blockData.filter { it.id in subSectionBlock.descendants } val blocks = verticalBlocks.flatMap { verticalBlock -> courseStructure.blockData.filter { it.id in verticalBlock.descendants && @@ -235,7 +193,7 @@ class DownloadDialogManager( } } val size = blocks.sumOf { getFileSize(it) } - if (size > 0) DownloadDialogItem(title = subSectionsBlock.displayName, size = size) else null + if (size > 0) DownloadDialogItem(title = subSectionBlock.displayName, size = size) else null } uiState.emit( @@ -252,12 +210,11 @@ class DownloadDialogManager( } } - private fun getFileSize(block: Block): Long { return when { - block.type == BlockType.VIDEO -> block.downloadModel?.size ?: 0 - block.isxBlock -> block.offlineDownload?.fileSize ?: 0 - else -> 0 + block.type == BlockType.VIDEO -> block.downloadModel?.size ?: 0L + block.isxBlock -> block.offlineDownload?.fileSize ?: 0L + else -> 0L } } } diff --git a/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt index 230b30deb..6fc72607f 100644 --- a/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt @@ -83,12 +83,14 @@ class CourseOfflineViewModel( val courseStructure = courseInteractor.getCourseStructureFromCache(courseId) val downloadModels = courseInteractor.getAllDownloadModels() val subSectionsBlocks = allBlocks.values.filter { it.type == BlockType.SEQUENTIAL } - val notDownloadedSubSectionBlocks = subSectionsBlocks.mapNotNull { subSectionsBlock -> - val verticalBlocks = allBlocks.values.filter { it.id in subSectionsBlock.descendants } + val notDownloadedSubSectionBlocks = subSectionsBlocks.mapNotNull { subSection -> + val verticalBlocks = allBlocks.values.filter { it.id in subSection.descendants } val notDownloadedBlocks = courseStructure.blockData.filter { block -> - block.id in verticalBlocks.flatMap { it.descendants } && block.isDownloadable && !downloadModels.any { it.id == block.id } + block.id in verticalBlocks.flatMap { it.descendants } && + block.isDownloadable && + downloadModels.none { it.id == block.id } } - if (notDownloadedBlocks.isNotEmpty()) subSectionsBlock else null + if (notDownloadedBlocks.isNotEmpty()) subSection else null } downloadDialogManager.showPopup( @@ -105,10 +107,9 @@ class CourseOfflineViewModel( } fun removeDownloadModel(downloadModel: DownloadModel, fragmentManager: FragmentManager) { - val icon = if (downloadModel.type == FileType.VIDEO) { - Icons.Outlined.SmartDisplay - } else { - Icons.AutoMirrored.Outlined.InsertDriveFile + val icon = when (downloadModel.type) { + FileType.VIDEO -> Icons.Outlined.SmartDisplay + else -> Icons.AutoMirrored.Outlined.InsertDriveFile } val downloadDialogItem = DownloadDialogItem( title = downloadModel.title, @@ -120,26 +121,25 @@ class CourseOfflineViewModel( fragmentManager = fragmentManager, removeDownloadModels = { super.removeBlockDownloadModel(downloadModel.id) - }, + } ) } fun deleteAll(fragmentManager: FragmentManager) { viewModelScope.launch { val downloadModels = courseInteractor.getAllDownloadModels().filter { it.courseId == courseId } + val totalSize = downloadModels.sumOf { it.size } val downloadDialogItem = DownloadDialogItem( title = courseTitle, - size = downloadModels.sumOf { it.size }, + size = totalSize, icon = Icons.AutoMirrored.Outlined.InsertDriveFile ) downloadDialogManager.showRemoveDownloadModelPopup( downloadDialogItem = downloadDialogItem, fragmentManager = fragmentManager, removeDownloadModels = { - downloadModels.forEach { - super.removeBlockDownloadModel(it.id) - } - }, + downloadModels.forEach { super.removeBlockDownloadModel(it.id) } + } ) } } @@ -148,9 +148,7 @@ class CourseOfflineViewModel( viewModelScope.launch { courseInteractor.getAllDownloadModels() .filter { it.courseId == courseId && it.downloadedState.isWaitingOrDownloading } - .forEach { - removeBlockDownloadModel(it.id) - } + .forEach { removeBlockDownloadModel(it.id) } } } @@ -159,57 +157,64 @@ class CourseOfflineViewModel( setBlocks(courseStructure.blockData) allBlocks.values .filter { it.type == BlockType.SEQUENTIAL } - .forEach { - addDownloadableChildrenForSequentialBlock(it) - } - + .forEach { addDownloadableChildrenForSequentialBlock(it) } } private fun getOfflineData() { viewModelScope.launch { val courseStructure = courseInteractor.getCourseStructureFromCache(courseId) - val downloadableFilesSize = getFilesSize(courseStructure.blockData) - if (downloadableFilesSize == 0L) return@launch - - courseInteractor.getDownloadModels().collect { - val downloadModels = it.filter { it.downloadedState.isDownloaded && it.courseId == courseId } - val downloadedModelsIds = downloadModels.map { it.id } - val downloadedBlocks = courseStructure.blockData.filter { it.id in downloadedModelsIds } - val downloadedFilesSize = getFilesSize(downloadedBlocks) - val realDownloadedFilesSize = downloadModels.sumOf { it.size } - val largestDownloads = downloadModels - .sortedByDescending { it.size } - .take(5) - - _uiState.update { - it.copy( - isHaveDownloadableBlocks = true, - largestDownloads = largestDownloads, - readyToDownloadSize = (downloadableFilesSize - downloadedFilesSize).toFileSize(1, false), - downloadedSize = realDownloadedFilesSize.toFileSize(1, false), - progressBarValue = downloadedFilesSize.toFloat() / downloadableFilesSize.toFloat() - ) - } + val totalDownloadableSize = getFilesSize(courseStructure.blockData) + + if (totalDownloadableSize == 0L) return@launch + + courseInteractor.getDownloadModels().collect { downloadModels -> + val completedDownloads = + downloadModels.filter { it.downloadedState.isDownloaded && it.courseId == courseId } + val completedDownloadIds = completedDownloads.map { it.id } + val downloadedBlocks = courseStructure.blockData.filter { it.id in completedDownloadIds } + + updateUIState( + totalDownloadableSize, + completedDownloads, + downloadedBlocks + ) } } } - private fun getFilesSize(block: List): Long { - return block.filter { it.isDownloadable }.sumOf { + private fun updateUIState( + totalDownloadableSize: Long, + completedDownloads: List, + downloadedBlocks: List + ) { + val downloadedSize = getFilesSize(downloadedBlocks) + val realDownloadedSize = completedDownloads.sumOf { it.size } + val largestDownloads = completedDownloads + .sortedByDescending { it.size } + .take(5) + + _uiState.update { + it.copy( + isHaveDownloadableBlocks = true, + largestDownloads = largestDownloads, + readyToDownloadSize = (totalDownloadableSize - downloadedSize).toFileSize(1, false), + downloadedSize = realDownloadedSize.toFileSize(1, false), + progressBarValue = downloadedSize.toFloat() / totalDownloadableSize.toFloat() + ) + } + } + + private fun getFilesSize(blocks: List): Long { + return blocks.filter { it.isDownloadable }.sumOf { when (it.downloadableType) { FileType.VIDEO -> { - val videoInfo = - it.studentViewData?.encodedVideos?.getPreferredVideoInfoForDownloading( - preferencesManager.videoSettings.videoDownloadQuality - ) - videoInfo?.fileSize ?: 0 - } - - FileType.X_BLOCK -> { - it.offlineDownload?.fileSize ?: 0 + it.studentViewData?.encodedVideos + ?.getPreferredVideoInfoForDownloading(preferencesManager.videoSettings.videoDownloadQuality) + ?.fileSize ?: 0 } - null -> 0 + FileType.X_BLOCK -> it.offlineDownload?.fileSize ?: 0 + else -> 0 } } }