diff --git a/app/src/main/java/org/openedx/app/AppActivity.kt b/app/src/main/java/org/openedx/app/AppActivity.kt index 4ea7b1884..d31102e80 100644 --- a/app/src/main/java/org/openedx/app/AppActivity.kt +++ b/app/src/main/java/org/openedx/app/AppActivity.kt @@ -12,9 +12,11 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.window.layout.WindowMetricsCalculator import io.branch.referral.Branch import io.branch.referral.Branch.BranchUniversalReferralInitListener +import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.openedx.app.databinding.ActivityAppBinding @@ -28,6 +30,7 @@ import org.openedx.core.presentation.global.WindowSizeHolder import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType import org.openedx.core.utils.Logger +import org.openedx.course.presentation.download.DownloadDialogManager import org.openedx.profile.presentation.ProfileRouter import org.openedx.whatsnew.WhatsNewManager import org.openedx.whatsnew.presentation.whatsnew.WhatsNewFragment @@ -49,6 +52,7 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { private val whatsNewManager by inject() private val corePreferencesManager by inject() private val profileRouter by inject() + private val downloadDialogManager by inject() private val branchLogger = Logger(BRANCH_TAG) @@ -73,7 +77,6 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { viewModel.logAppLaunchEvent() setContentView(binding.root) val container = binding.rootLayout - viewModel.fragmentManager = supportFragmentManager container.addView(object : View(this) { override fun onConfigurationChanged(newConfig: Configuration?) { @@ -141,6 +144,15 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { viewModel.logoutUser.observe(this) { profileRouter.restartApp(supportFragmentManager, viewModel.isLogistrationEnabled) } + + lifecycleScope.launch { + viewModel.downloadFailedDialog.collect { + downloadDialogManager.showDownloadFailedPopup( + downloadModel = it.downloadModel, + fragmentManager = supportFragmentManager, + ) + } + } } override fun onStart() { diff --git a/app/src/main/java/org/openedx/app/AppViewModel.kt b/app/src/main/java/org/openedx/app/AppViewModel.kt index fe9f03eca..1aef355f8 100644 --- a/app/src/main/java/org/openedx/app/AppViewModel.kt +++ b/app/src/main/java/org/openedx/app/AppViewModel.kt @@ -6,6 +6,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.viewModelScope import androidx.room.RoomDatabase import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.openedx.app.deeplink.DeepLink @@ -19,7 +22,6 @@ import org.openedx.core.data.storage.CorePreferences import org.openedx.core.system.notifier.DownloadFailed import org.openedx.core.system.notifier.DownloadNotifier import org.openedx.core.utils.FileUtil -import org.openedx.course.presentation.download.DownloadDialogManager class AppViewModel( private val config: Config, @@ -31,13 +33,17 @@ class AppViewModel( private val deepLinkRouter: DeepLinkRouter, private val fileUtil: FileUtil, private val downloadNotifier: DownloadNotifier, - private val downloadDialogManager: DownloadDialogManager, ) : BaseViewModel() { private val _logoutUser = SingleEventLiveData() val logoutUser: LiveData get() = _logoutUser + private val _downloadFailedDialog = MutableSharedFlow() + val downloadFailedDialog: SharedFlow + get() = _downloadFailedDialog.asSharedFlow() + + val isLogistrationEnabled get() = config.isPreLoginExperienceEnabled() private var logoutHandledAt: Long = 0 @@ -45,8 +51,6 @@ class AppViewModel( val isBranchEnabled get() = config.getBranchConfig().enabled private val canResetAppDirectory get() = preferencesManager.canResetAppDirectory - var fragmentManager: FragmentManager? = null - override fun onCreate(owner: LifecycleOwner) { super.onCreate(owner) setUserId() @@ -69,12 +73,7 @@ class AppViewModel( viewModelScope.launch { downloadNotifier.notifier.collect { event -> if (event is DownloadFailed) { - fragmentManager?.let { - downloadDialogManager.showDownloadFailedPopup( - downloadModel = event.downloadModel, - fragmentManager = it, - ) - } + _downloadFailedDialog.emit(event) } } } diff --git a/app/src/main/java/org/openedx/app/MainViewModel.kt b/app/src/main/java/org/openedx/app/MainViewModel.kt index a9a4d24ea..ed80d1d6f 100644 --- a/app/src/main/java/org/openedx/app/MainViewModel.kt +++ b/app/src/main/java/org/openedx/app/MainViewModel.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import org.openedx.app.worker.OfflineProgressSyncScheduler import org.openedx.core.BaseViewModel import org.openedx.core.config.Config import org.openedx.core.system.notifier.DiscoveryNotifier @@ -21,7 +20,6 @@ class MainViewModel( private val config: Config, private val notifier: DiscoveryNotifier, private val analytics: AppAnalytics, - private val offlineProgressSyncScheduler: OfflineProgressSyncScheduler, ) : BaseViewModel() { private val _isBottomBarEnabled = MutableLiveData(true) @@ -37,7 +35,6 @@ class MainViewModel( override fun onCreate(owner: LifecycleOwner) { super.onCreate(owner) - offlineProgressSyncScheduler.scheduleHourlySync() notifier.notifier .onEach { if (it is NavigationToDiscovery) { diff --git a/app/src/main/java/org/openedx/app/di/AppModule.kt b/app/src/main/java/org/openedx/app/di/AppModule.kt index 88e9a1ec5..c4ac539cd 100644 --- a/app/src/main/java/org/openedx/app/di/AppModule.kt +++ b/app/src/main/java/org/openedx/app/di/AppModule.kt @@ -19,7 +19,6 @@ import org.openedx.app.deeplink.DeepLinkRouter import org.openedx.app.room.AppDatabase import org.openedx.app.room.DATABASE_NAME import org.openedx.app.system.notifier.AppNotifier -import org.openedx.app.worker.OfflineProgressSyncScheduler import org.openedx.auth.presentation.AgreementProvider import org.openedx.auth.presentation.AuthAnalytics import org.openedx.auth.presentation.AuthRouter @@ -56,6 +55,7 @@ import org.openedx.course.data.storage.CoursePreferences import org.openedx.course.presentation.CourseAnalytics import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.download.DownloadDialogManager +import org.openedx.course.worker.OfflineProgressSyncScheduler import org.openedx.dashboard.presentation.DashboardAnalytics import org.openedx.dashboard.presentation.DashboardRouter import org.openedx.discovery.presentation.DiscoveryAnalytics diff --git a/app/src/main/java/org/openedx/app/di/ScreenModule.kt b/app/src/main/java/org/openedx/app/di/ScreenModule.kt index b93633c5a..a8ad5272b 100644 --- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt +++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt @@ -80,10 +80,9 @@ val screenModule = module { get(), get(), get(), - get(), ) } - viewModel { MainViewModel(get(), get(), get(), get()) } + viewModel { MainViewModel(get(), get(), get()) } factory { AuthRepository(get(), get(), get()) } factory { AuthInteractor(get()) } @@ -438,7 +437,8 @@ val screenModule = module { get(), get(), get(), - get() + get(), + get(), ) } diff --git a/app/src/main/java/org/openedx/app/worker/OfflineProgressSyncScheduler.kt b/app/src/main/java/org/openedx/app/worker/OfflineProgressSyncScheduler.kt deleted file mode 100644 index 7b3fce36d..000000000 --- a/app/src/main/java/org/openedx/app/worker/OfflineProgressSyncScheduler.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.openedx.app.worker - -import android.content.Context -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import java.util.concurrent.TimeUnit - -class OfflineProgressSyncScheduler(private val context: Context) { - - fun scheduleHourlySync() { - val periodicWorkRequest = PeriodicWorkRequestBuilder(1, TimeUnit.HOURS) - .addTag(OfflineProgressSyncWorker.WORKER_TAG) - .build() - - WorkManager.getInstance(context).enqueueUniquePeriodicWork( - OfflineProgressSyncWorker.WORKER_TAG, - ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - periodicWorkRequest - ) - } -} diff --git a/core/src/main/java/org/openedx/core/data/api/CourseApi.kt b/core/src/main/java/org/openedx/core/data/api/CourseApi.kt index 7d834463c..9a6826f79 100644 --- a/core/src/main/java/org/openedx/core/data/api/CourseApi.kt +++ b/core/src/main/java/org/openedx/core/data/api/CourseApi.kt @@ -84,6 +84,6 @@ interface CourseApi { suspend fun submitOfflineXBlockProgress( @Path("course_id") courseId: String, @Path("block_id") blockId: String, - @FieldMap(encoded = false) progress: Map + @FieldMap progress: Map ) } diff --git a/core/src/main/java/org/openedx/core/extension/LongExt.kt b/core/src/main/java/org/openedx/core/extension/LongExt.kt index 2cffed100..6a6d757b5 100644 --- a/core/src/main/java/org/openedx/core/extension/LongExt.kt +++ b/core/src/main/java/org/openedx/core/extension/LongExt.kt @@ -9,7 +9,7 @@ fun Long.toFileSize(round: Int = 2, space: Boolean = true): String { val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") val digitGroups = (log10(this.toDouble()) / log10(1024.0)).toInt() val size = this / 1024.0.pow(digitGroups.toDouble()) - val formatString = if (size % 1 == 0.0) "%.0f" else "%.${round}f" + val formatString = if (size % 1 < 0.05) "%.0f" else "%.${round}f" return String.format(formatString, size) + if (space) " " else "" + units[digitGroups] } catch (e: Exception) { println(e.toString()) diff --git a/core/src/main/java/org/openedx/core/module/DownloadWorker.kt b/core/src/main/java/org/openedx/core/module/DownloadWorker.kt index 99297e452..2186dbfc6 100644 --- a/core/src/main/java/org/openedx/core/module/DownloadWorker.kt +++ b/core/src/main/java/org/openedx/core/module/DownloadWorker.kt @@ -135,9 +135,14 @@ class DownloadWorker( when (downloadResult) { DownloadResult.SUCCESS -> { val updatedModel = downloadHelper.updateDownloadStatus(downloadTask) - downloadDao.updateDownloadModel( - DownloadModelEntity.createFrom(updatedModel) - ) + if (updatedModel == null) { + downloadDao.removeDownloadModel(downloadTask.id) + downloadError.add(downloadTask) + } else { + downloadDao.updateDownloadModel( + DownloadModelEntity.createFrom(updatedModel) + ) + } } DownloadResult.CANCELED -> { diff --git a/core/src/main/java/org/openedx/core/module/db/DownloadModel.kt b/core/src/main/java/org/openedx/core/module/db/DownloadModel.kt index 3f2c233fe..da736ba28 100644 --- a/core/src/main/java/org/openedx/core/module/db/DownloadModel.kt +++ b/core/src/main/java/org/openedx/core/module/db/DownloadModel.kt @@ -32,4 +32,4 @@ enum class DownloadedState { enum class FileType { VIDEO, X_BLOCK -} \ No newline at end of file +} diff --git a/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt b/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt index 861de8301..146cc1fc3 100644 --- a/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt +++ b/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt @@ -73,7 +73,6 @@ abstract class AbstractDownloader : KoinComponent { } } - suspend fun cancelDownloading() { isCanceled = true withContext(Dispatchers.IO) { @@ -95,5 +94,4 @@ abstract class AbstractDownloader : KoinComponent { enum class DownloadResult { SUCCESS, CANCELED, ERROR } - -} \ No newline at end of file +} diff --git a/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt b/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt index 34bc3ec12..40d3f1f41 100644 --- a/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt +++ b/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt @@ -217,17 +217,6 @@ abstract class BaseDownloadViewModel( } } - protected fun addDownloadableChildrenForVerticalBlock(verticalBlock: Block) { - for (unitBlockId in verticalBlock.descendants) { - val block = allBlocks[unitBlockId] - if (block?.isDownloadable == true) { - val id = verticalBlock.id - val children = downloadableChildrenMap[id] ?: listOf() - downloadableChildrenMap[id] = children + block.id - } - } - } - fun logBulkDownloadToggleEvent(toggle: Boolean) { logEvent( CoreAnalyticsEvent.VIDEO_BULK_DOWNLOAD_TOGGLE, diff --git a/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt b/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt index c3ce97700..7c687f58e 100644 --- a/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt +++ b/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt @@ -71,7 +71,7 @@ class DownloadHelper( } } - suspend fun updateDownloadStatus(downloadModel: DownloadModel): DownloadModel { + suspend fun updateDownloadStatus(downloadModel: DownloadModel): DownloadModel? { return when (downloadModel.type) { FileType.VIDEO -> { downloadModel.copy( @@ -81,13 +81,33 @@ class DownloadHelper( } FileType.X_BLOCK -> { - val unzippedFolderPath = fileUtil.unzipFile(downloadModel.path) + val unzippedFolderPath = fileUtil.unzipFile(downloadModel.path) ?: return null downloadModel.copy( downloadedState = DownloadedState.DOWNLOADED, - size = File(unzippedFolderPath ?: "").length(), - path = unzippedFolderPath ?: "" + size = calculateDirectorySize(File(unzippedFolderPath)), + path = unzippedFolderPath ) } } } + + private fun calculateDirectorySize(directory: File): Long { + var size: Long = 0 + + if (directory.exists()) { + val files = directory.listFiles() + + if (files != null) { + for (file in files) { + size += if (file.isDirectory) { + calculateDirectorySize(file) + } else { + file.length() + } + } + } + } + + return size + } } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 87c1b0339..0c5392a83 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -193,4 +193,5 @@ Confirm Download Edit Offline Progress Sync + Close diff --git a/core/src/openedx/org/openedx/core/ui/theme/Colors.kt b/core/src/openedx/org/openedx/core/ui/theme/Colors.kt index c0c91c975..69f550018 100644 --- a/core/src/openedx/org/openedx/core/ui/theme/Colors.kt +++ b/core/src/openedx/org/openedx/core/ui/theme/Colors.kt @@ -8,7 +8,7 @@ val light_secondary = Color(0xFF94D3DD) val light_secondary_variant = Color(0xFF94D3DD) val light_background = Color.White val light_surface = Color(0xFFF7F7F8) -val light_error = Color(0xFFFF3D71) +val light_error = Color(0xFFE8174F) val light_onPrimary = Color.White val light_onSecondary = Color.White val light_onBackground = Color.Black diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogFragment.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogFragment.kt index decbf3597..5f811d18a 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogFragment.kt @@ -78,7 +78,7 @@ class DownloadConfirmDialogFragment : DialogFragment() { id = R.string.course_download_on_cellural_dialog_description, sizeSumString ), - icon = painterResource(id = org.openedx.core.R.drawable.core_ic_warning), + icon = painterResource(id = coreR.drawable.core_ic_warning), ) DownloadConfirmDialogType.REMOVE -> DownloadDialogResource( @@ -214,7 +214,7 @@ private fun DownloadConfirmDialogView( ) OpenEdXOutlinedButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = org.openedx.core.R.string.core_cancel), + text = stringResource(id = coreR.string.core_cancel), backgroundColor = MaterialTheme.appColors.background, borderColor = MaterialTheme.appColors.primaryButtonBackground, textColor = MaterialTheme.appColors.primaryButtonBackground, diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogType.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogType.kt index 4418c322c..9c0833ff3 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogType.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadConfirmDialogType.kt @@ -6,4 +6,4 @@ import kotlinx.parcelize.Parcelize @Parcelize enum class DownloadConfirmDialogType : Parcelable { DOWNLOAD_ON_CELLULAR, CONFIRM, REMOVE -} \ No newline at end of file +} 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 e529c0ed0..5bff7b680 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 @@ -22,7 +22,7 @@ class DownloadDialogManager( ) { companion object { - const val MAX_CELLULAR_SIZE = 100000000 // 100MB + const val MAX_CELLULAR_SIZE = 104857600 // 100MB const val DOWNLOAD_SIZE_FACTOR = 2 // Multiplier to match required disk size } @@ -86,7 +86,7 @@ class DownloadDialogManager( ) } - !corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() && uiState.sizeSum >= MAX_CELLULAR_SIZE -> { + !corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> { val dialog = DownloadConfirmDialogFragment.newInstance( dialogType = DownloadConfirmDialogType.DOWNLOAD_ON_CELLULAR, uiState = uiState @@ -220,9 +220,11 @@ class DownloadDialogManager( ) { CoroutineScope(Dispatchers.IO).launch { val courseStructure = interactor.getCourseStructure(courseId, false) + val downloadModels = interactor.getAllDownloadModels().map { it.id } val downloadDialogItems = subSectionsBlocks.mapNotNull { subSectionsBlock -> val verticalBlocks = courseStructure.blockData.filter { it.id in subSectionsBlock.descendants } - val blocks = courseStructure.blockData.filter { it.id in verticalBlocks.flatMap { it.descendants } } + val blocks = + courseStructure.blockData.filter { it.id in verticalBlocks.flatMap { it.descendants } && it.id !in downloadModels } val size = blocks.sumOf { getFileSize(it) } if (size > 0) DownloadDialogItem(title = subSectionsBlock.displayName, size = size) else null } diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogUIState.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogUIState.kt index 95703d06d..b58e856bd 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogUIState.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogUIState.kt @@ -14,4 +14,4 @@ data class DownloadDialogUIState( val fragmentManager: @RawValue FragmentManager, val removeDownloadModels: () -> Unit, val saveDownloadModels: () -> Unit -) : Parcelable \ No newline at end of file +) : Parcelable diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogFragment.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogFragment.kt index 9ed4bdada..368fea067 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogFragment.kt @@ -170,7 +170,7 @@ private fun DownloadErrorDialogView( } OpenEdXOutlinedButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = org.openedx.core.R.string.core_cancel), + text = stringResource(id = coreR.string.core_close), backgroundColor = MaterialTheme.appColors.background, borderColor = MaterialTheme.appColors.primaryButtonBackground, textColor = MaterialTheme.appColors.primaryButtonBackground, diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogType.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogType.kt index 12341c9a7..85f01cf1a 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogType.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadErrorDialogType.kt @@ -6,4 +6,4 @@ import kotlinx.parcelize.Parcelize @Parcelize enum class DownloadErrorDialogType : Parcelable { NO_CONNECTION, WIFI_REQUIRED, DOWNLOAD_FAILED -} \ No newline at end of file +} diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt index 4bbe8590b..2281735b3 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment -import org.openedx.core.R import org.openedx.core.extension.parcelable import org.openedx.core.extension.toFileSize import org.openedx.core.presentation.dialog.DefaultDialogBox @@ -51,8 +50,10 @@ import org.openedx.core.ui.OpenEdXOutlinedButton import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appTypography +import org.openedx.course.R import org.openedx.course.domain.model.DownloadDialogResource import org.openedx.course.presentation.download.DownloadDialogManager.Companion.DOWNLOAD_SIZE_FACTOR +import org.openedx.core.R as coreR class DownloadStorageErrorDialogFragment : DialogFragment() { @@ -67,9 +68,9 @@ class DownloadStorageErrorDialogFragment : DialogFragment() { OpenEdXTheme { val uiState = requireArguments().parcelable(ARG_UI_STATE) ?: return@OpenEdXTheme val downloadDialogResource = DownloadDialogResource( - title = stringResource(id = org.openedx.course.R.string.course_device_storage_full), - description = stringResource(id = org.openedx.course.R.string.course_download_device_storage_full_dialog_description), - icon = painterResource(id = org.openedx.course.R.drawable.course_ic_error), + title = stringResource(id = R.string.course_device_storage_full), + description = stringResource(id = R.string.course_download_device_storage_full_dialog_description), + icon = painterResource(id = R.drawable.course_ic_error), ) DownloadStorageErrorDialogView( @@ -153,7 +154,7 @@ private fun DownloadStorageErrorDialogView( ) OpenEdXOutlinedButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.core_cancel), + text = stringResource(id = coreR.string.core_cancel), backgroundColor = MaterialTheme.appColors.background, borderColor = MaterialTheme.appColors.primaryButtonBackground, textColor = MaterialTheme.appColors.primaryButtonBackground, @@ -229,7 +230,7 @@ private fun StorageBar( ) { Text( text = stringResource( - org.openedx.course.R.string.course_used_free_storage, + R.string.course_used_free_storage, usedSpace.toFileSize(1, false), freeSpace.toFileSize(1, false) ), @@ -254,7 +255,7 @@ private fun DownloadStorageErrorDialogViewPreview() { downloadDialogResource = DownloadDialogResource( title = "Title", description = "Description Description Description Description Description Description Description ", - icon = painterResource(id = org.openedx.course.R.drawable.course_ic_error) + icon = painterResource(id = R.drawable.course_ic_error) ), uiState = DownloadDialogUIState( downloadDialogItems = listOf( @@ -277,4 +278,4 @@ private fun DownloadStorageErrorDialogViewPreview() { onCancelClick = {} ) } -} \ No newline at end of file +} diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadView.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadView.kt index 722528e9f..2a760c772 100644 --- a/course/src/main/java/org/openedx/course/presentation/download/DownloadView.kt +++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadView.kt @@ -35,10 +35,12 @@ fun DownloadDialogItem( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( + modifier = Modifier + .size(24.dp) + .align(Alignment.Top), painter = icon, tint = MaterialTheme.appColors.textDark, contentDescription = null, - modifier = Modifier.size(24.dp) ) Text( modifier = Modifier.weight(1f), 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 66500cd1c..0467ece90 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 @@ -171,6 +171,7 @@ class CourseOfflineViewModel( 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) @@ -180,7 +181,7 @@ class CourseOfflineViewModel( isHaveDownloadableBlocks = true, largestDownloads = largestDownloads, readyToDownloadSize = (downloadableFilesSize - downloadedFilesSize).toFileSize(1, false), - downloadedSize = downloadedFilesSize.toFileSize(1, false), + downloadedSize = realDownloadedFilesSize.toFileSize(1, false), progressBarValue = downloadedFilesSize.toFloat() / downloadableFilesSize.toFloat() ) } diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt index 204f33ced..63752fdaa 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt @@ -402,7 +402,11 @@ class CourseOutlineViewModel( val notDownloadedBlocks = allBlocks.values.filter { it.id in verticalBlocks.flatMap { it.descendants } && it.isDownloadable && !isBlockDownloaded(it.id) } - if (notDownloadedBlocks.isNotEmpty()) subSectionsBlock else null + if (notDownloadedBlocks.isNotEmpty()) { + subSectionsBlock + } else { + null + } } val requiredSubSections = notDownloadedSubSectionBlocks.ifEmpty { @@ -415,7 +419,9 @@ class CourseOutlineViewModel( courseRouter.navigateToDownloadQueue(fragmentManager, downloadableChildren) } else { downloadableChildren.forEach { - removeBlockDownloadModel(it) + if (!isBlockDownloaded(it)) { + removeBlockDownloadModel(it) + } } } } else { diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt index 71bec4461..eda5ab411 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt @@ -149,51 +149,51 @@ class HtmlUnitFragment : Fragment() { contentAlignment = Alignment.TopCenter ) { if (uiState.isLoadingEnabled) { - if (hasInternetConnection || fromDownloadedContent) { - HTMLContentView( - uiState = uiState, - windowSize = windowSize, - url = url, - cookieManager = viewModel.cookieManager, - apiHostURL = viewModel.apiHostURL, - isLoading = isLoading, - injectJSList = injectJSList, - onCompletionSet = { - viewModel.notifyCompletionSet() - }, - onWebPageLoading = { - isLoading = true - }, - onWebPageLoaded = { - isLoading = false - if (isAdded) viewModel.setWebPageLoaded(requireContext().assets) - }, - saveXBlockProgress = { jsonProgress -> - viewModel.saveXBlockProgress(jsonProgress) + if (hasInternetConnection || fromDownloadedContent) { + HTMLContentView( + uiState = uiState, + windowSize = windowSize, + url = url, + cookieManager = viewModel.cookieManager, + apiHostURL = viewModel.apiHostURL, + isLoading = isLoading, + injectJSList = injectJSList, + onCompletionSet = { + viewModel.notifyCompletionSet() + }, + onWebPageLoading = { + isLoading = true + }, + onWebPageLoaded = { + isLoading = false + if (isAdded) viewModel.setWebPageLoaded(requireContext().assets) + }, + saveXBlockProgress = { jsonProgress -> + viewModel.saveXBlockProgress(jsonProgress) + } + ) + } else { + ConnectionErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background) + ) { + hasInternetConnection = viewModel.isOnline } - ) - } else { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background) - ) { - hasInternetConnection = viewModel.isOnline } - } - if (isLoading && hasInternetConnection) { - Box( - modifier = Modifier - .fillMaxSize() - .zIndex(1f), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator(color = MaterialTheme.appColors.primary) + if (isLoading && hasInternetConnection) { + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(1f), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = MaterialTheme.appColors.primary) + } } } } - } } } } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt index f407ac1cc..2dc14424c 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt @@ -3,4 +3,4 @@ package org.openedx.course.presentation.unit.html data class HtmlUnitUIState( val jsonProgress: String?, val isLoadingEnabled: Boolean -) \ No newline at end of file +) diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt index 6bb302eed..24aefd504 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt @@ -15,6 +15,7 @@ import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.system.notifier.CourseCompletionSet import org.openedx.core.system.notifier.CourseNotifier import org.openedx.course.domain.interactor.CourseInteractor +import org.openedx.course.worker.OfflineProgressSyncScheduler class HtmlUnitViewModel( private val blockId: String, @@ -24,6 +25,7 @@ class HtmlUnitViewModel( private val networkConnection: NetworkConnection, private val notifier: CourseNotifier, private val courseInteractor: CourseInteractor, + private val offlineProgressSyncScheduler: OfflineProgressSyncScheduler ) : BaseViewModel() { private val _uiState = MutableStateFlow(HtmlUnitUIState(null, false)) @@ -65,6 +67,7 @@ class HtmlUnitViewModel( fun saveXBlockProgress(jsonProgress: String) { viewModelScope.launch { courseInteractor.saveXBlockProgress(blockId, courseId, jsonProgress) + offlineProgressSyncScheduler.scheduleSync() } } diff --git a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt index 12dd16261..4756ac3bb 100644 --- a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt @@ -252,7 +252,9 @@ class CourseVideoViewModel( courseRouter.navigateToDownloadQueue(fragmentManager, downloadableChildren) } else { downloadableChildren.forEach { - removeBlockDownloadModel(it) + if (!isBlockDownloaded(it)) { + removeBlockDownloadModel(it) + } } } } else { diff --git a/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncScheduler.kt b/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncScheduler.kt new file mode 100644 index 000000000..25fad94af --- /dev/null +++ b/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncScheduler.kt @@ -0,0 +1,28 @@ +package org.openedx.course.worker + +import android.content.Context +import androidx.work.Constraints +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager + +class OfflineProgressSyncScheduler(private val context: Context) { + + fun scheduleSync() { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .build() + + val workRequest = OneTimeWorkRequestBuilder() + .addTag(OfflineProgressSyncWorker.WORKER_TAG) + .setConstraints(constraints) + .build() + + WorkManager.getInstance(context).enqueueUniqueWork( + OfflineProgressSyncWorker.WORKER_TAG, + ExistingWorkPolicy.REPLACE, + workRequest + ) + } +} diff --git a/app/src/main/java/org/openedx/app/worker/OfflineProgressSyncWorker.kt b/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt similarity index 98% rename from app/src/main/java/org/openedx/app/worker/OfflineProgressSyncWorker.kt rename to course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt index 25d1c6b1d..0b448d6b3 100644 --- a/app/src/main/java/org/openedx/app/worker/OfflineProgressSyncWorker.kt +++ b/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt @@ -1,4 +1,4 @@ -package org.openedx.app.worker +package org.openedx.course.worker import android.app.NotificationChannel import android.app.NotificationManager