Skip to content

Commit

Permalink
Feat: Download all videos together (openedx#234)
Browse files Browse the repository at this point in the history
* feat: ability to download all videos

* feat: confirmation dialog for downloading videos larger than 1 GB

* refactor: renamed the Video tab to Videos

* refactor: hide All videos download element if there is no videos to download

* fix: bug when unable to see all videos

* fix: lags when updating downloading state

* refactor: changed the type of allBlocks to HashMap in the BaseDownloadViewModel

* feat: confirmation dialog when trying to remove all downloads

* feat: show download progress on the download queue screen

* feat: view downloads for subsection

* refactor: changed how all modules are download

* refactor: optimized way to remove models

* refactor: changed the way the download progress is displayed

* feat: added confirmation dialogs

* feat: show Untitled title if the block has no title

* refactor: removed unused logs

* fix: after rebase

* fix: after rebase

* refactor: change the name of the Discussion tab to Discussions

* fix: fixed issues after PR
  • Loading branch information
dixidroid authored Feb 25, 2024
1 parent dedb010 commit 8f6aaf4
Show file tree
Hide file tree
Showing 63 changed files with 1,994 additions and 688 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<queries>
<intent>
Expand Down
12 changes: 9 additions & 3 deletions app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import org.openedx.core.presentation.course.CourseViewMode
import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRouter
import org.openedx.core.presentation.global.app_upgrade.UpgradeRequiredFragment
import org.openedx.core.presentation.global.webview.WebContentFragment
import org.openedx.core.presentation.settings.VideoQualityFragment
import org.openedx.core.presentation.settings.VideoQualityType
import org.openedx.course.presentation.CourseRouter
import org.openedx.course.presentation.container.CourseContainerFragment
import org.openedx.course.presentation.container.NoAccessCourseContainerFragment
Expand All @@ -24,6 +26,7 @@ import org.openedx.course.presentation.section.CourseSectionFragment
import org.openedx.course.presentation.unit.container.CourseUnitContainerFragment
import org.openedx.course.presentation.unit.video.VideoFullScreenFragment
import org.openedx.course.presentation.unit.video.YoutubeVideoFullScreenFragment
import org.openedx.course.settings.download.DownloadQueueFragment
import org.openedx.dashboard.presentation.DashboardRouter
import org.openedx.dashboard.presentation.program.ProgramFragment
import org.openedx.discovery.presentation.DiscoveryRouter
Expand All @@ -44,7 +47,6 @@ import org.openedx.profile.presentation.anothers_account.AnothersProfileFragment
import org.openedx.profile.presentation.delete.DeleteProfileFragment
import org.openedx.profile.presentation.edit.EditProfileFragment
import org.openedx.profile.presentation.profile.ProfileFragment
import org.openedx.profile.presentation.settings.video.VideoQualityFragment
import org.openedx.profile.presentation.settings.video.VideoSettingsFragment
import org.openedx.whatsnew.WhatsNewRouter
import org.openedx.whatsnew.presentation.whatsnew.WhatsNewFragment
Expand Down Expand Up @@ -72,6 +74,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, LogistrationFragment.newInstance(courseId))
}

override fun navigateToDownloadQueue(fm: FragmentManager, descendants: List<String>) {
replaceFragmentWithBackStack(fm, DownloadQueueFragment.newInstance(descendants))
}

override fun navigateToRestorePassword(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, RestorePasswordFragment())
}
Expand Down Expand Up @@ -325,8 +331,8 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, VideoSettingsFragment())
}

override fun navigateToVideoQuality(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, VideoQualityFragment())
override fun navigateToVideoQuality(fm: FragmentManager, videoQualityType: VideoQualityType) {
replaceFragmentWithBackStack(fm, VideoQualityFragment.newInstance(videoQualityType.name))
}

override fun navigateToDeleteAccount(fm: FragmentManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.openedx.app.BuildConfig
import org.openedx.core.data.model.User
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.data.storage.InAppReviewPreferences
import org.openedx.core.domain.model.VideoQuality
import org.openedx.core.domain.model.VideoSettings
import org.openedx.profile.data.model.Account
import org.openedx.profile.data.storage.ProfilePreferences
Expand All @@ -23,7 +24,9 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
}.apply()
}

private fun getString(key: String): String = sharedPreferences.getString(key, "") ?: ""
private fun getString(key: String, defValue: String = ""): String {
return sharedPreferences.getString(key, defValue) ?: defValue
}

private fun saveLong(key: String, value: Long) {
sharedPreferences.edit().apply {
Expand All @@ -39,7 +42,9 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
}.apply()
}

private fun getBoolean(key: String): Boolean = sharedPreferences.getBoolean(key, false)
private fun getBoolean(key: String, defValue: Boolean = false): Boolean {
return sharedPreferences.getBoolean(key, defValue)
}

override fun clear() {
sharedPreferences.edit().apply {
Expand Down Expand Up @@ -90,13 +95,22 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences

override var videoSettings: VideoSettings
set(value) {
val videoSettingsJson = Gson().toJson(value)
saveString(VIDEO_SETTINGS, videoSettingsJson)
saveBoolean(VIDEO_SETTINGS_WIFI_DOWNLOAD_ONLY, value.wifiDownloadOnly)
saveString(VIDEO_SETTINGS_STREAMING_QUALITY, value.videoStreamingQuality.name)
saveString(VIDEO_SETTINGS_DOWNLOAD_QUALITY, value.videoDownloadQuality.name)
}
get() {
val videoSettingsString = getString(VIDEO_SETTINGS)
return Gson().fromJson(videoSettingsString, VideoSettings::class.java)
?: VideoSettings.default
val wifiDownloadOnly = getBoolean(VIDEO_SETTINGS_WIFI_DOWNLOAD_ONLY, defValue = true)
val streamingQualityString =
getString(VIDEO_SETTINGS_STREAMING_QUALITY, defValue = VideoQuality.AUTO.name)
val downloadQualityString =
getString(VIDEO_SETTINGS_DOWNLOAD_QUALITY, defValue = VideoQuality.AUTO.name)

return VideoSettings(
wifiDownloadOnly = wifiDownloadOnly,
videoStreamingQuality = VideoQuality.valueOf(streamingQualityString),
videoDownloadQuality = VideoQuality.valueOf(downloadQualityString)
)
}

override var lastWhatsNewVersion: String
Expand Down Expand Up @@ -132,9 +146,11 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences
private const val EXPIRES_IN = "expires_in"
private const val USER = "user"
private const val ACCOUNT = "account"
private const val VIDEO_SETTINGS = "video_settings"
private const val LAST_WHATS_NEW_VERSION = "last_whats_new_version"
private const val LAST_REVIEW_VERSION = "last_review_version"
private const val APP_WAS_POSITIVE_RATED = "app_was_positive_rated"
private const val VIDEO_SETTINGS_WIFI_DOWNLOAD_ONLY = "video_settings_wifi_download_only"
private const val VIDEO_SETTINGS_STREAMING_QUALITY = "video_settings_streaming_quality"
private const val VIDEO_SETTINGS_DOWNLOAD_QUALITY = "video_settings_download_quality"
}
}
4 changes: 4 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 @@ -42,6 +42,8 @@ import org.openedx.core.system.notifier.AppUpgradeNotifier
import org.openedx.core.system.notifier.CourseNotifier
import org.openedx.course.domain.interactor.CourseInteractor
import org.openedx.dashboard.notifier.DashboardNotifier
import org.openedx.core.system.notifier.DownloadNotifier
import org.openedx.core.system.notifier.VideoNotifier
import org.openedx.course.presentation.CourseAnalytics
import org.openedx.course.presentation.CourseRouter
import org.openedx.dashboard.presentation.dashboard.DashboardAnalytics
Expand Down Expand Up @@ -79,7 +81,9 @@ val appModule = module {
single { DiscussionNotifier() }
single { ProfileNotifier() }
single { AppUpgradeNotifier() }
single { DownloadNotifier() }
single { DashboardNotifier() }
single { VideoNotifier() }

single { AppRouter() }
single<AuthRouter> { get<AppRouter>() }
Expand Down
16 changes: 14 additions & 2 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.openedx.auth.presentation.signin.SignInViewModel
import org.openedx.auth.presentation.signup.SignUpViewModel
import org.openedx.core.Validator
import org.openedx.core.presentation.dialog.selectorbottomsheet.SelectDialogViewModel
import org.openedx.core.presentation.settings.VideoQualityViewModel
import org.openedx.course.data.repository.CourseRepository
import org.openedx.course.domain.interactor.CourseInteractor
import org.openedx.course.presentation.container.CourseContainerViewModel
Expand All @@ -27,6 +28,7 @@ import org.openedx.course.presentation.unit.video.EncodedVideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoViewModel
import org.openedx.course.presentation.videos.CourseVideoViewModel
import org.openedx.course.settings.download.DownloadQueueViewModel
import org.openedx.dashboard.data.repository.DashboardRepository
import org.openedx.dashboard.domain.interactor.DashboardInteractor
import org.openedx.dashboard.presentation.dashboard.DashboardViewModel
Expand All @@ -52,7 +54,6 @@ import org.openedx.profile.presentation.anothers_account.AnothersProfileViewMode
import org.openedx.profile.presentation.delete.DeleteProfileViewModel
import org.openedx.profile.presentation.edit.EditProfileViewModel
import org.openedx.profile.presentation.profile.ProfileViewModel
import org.openedx.profile.presentation.settings.video.VideoQualityViewModel
import org.openedx.profile.presentation.settings.video.VideoSettingsViewModel
import org.openedx.whatsnew.presentation.whatsnew.WhatsNewViewModel

Expand Down Expand Up @@ -120,7 +121,7 @@ val screenModule = module {
}
viewModel { (account: Account) -> EditProfileViewModel(get(), get(), get(), get(), account) }
viewModel { VideoSettingsViewModel(get(), get()) }
viewModel { VideoQualityViewModel(get(), get()) }
viewModel { (qualityType: String) -> VideoQualityViewModel(get(), get(), qualityType) }
viewModel { DeleteProfileViewModel(get(), get(), get(), get()) }
viewModel { (username: String) -> AnothersProfileViewModel(get(), get(), username) }

Expand Down Expand Up @@ -210,6 +211,7 @@ val screenModule = module {
get(),
get(),
get(),
get(),
get()
)
}
Expand Down Expand Up @@ -293,6 +295,16 @@ val screenModule = module {
get()
)
}

viewModel { (descendants: List<String>) ->
DownloadQueueViewModel(
descendants,
get(),
get(),
get(),
get()
)
}
viewModel { HtmlUnitViewModel(get(), get(), get(), get()) }

viewModel { ProgramViewModel(get(), get(), get(), get(), get(), get(), get()) }
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/org/openedx/core/AppDataConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ object AppDataConstants {

const val VIDEO_FORMAT_M3U8 = ".m3u8"
const val VIDEO_FORMAT_MP4 = ".mp4"
}

// Equal 1GB
const val DOWNLOADS_CONFIRMATION_SIZE = 1024 * 1024 * 1024L
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import org.openedx.core.R

data class VideoSettings(
val wifiDownloadOnly: Boolean,
val videoQuality: VideoQuality
val videoStreamingQuality: VideoQuality,
val videoDownloadQuality: VideoQuality
) {
companion object {
val default = VideoSettings(true, VideoQuality.AUTO)
val default = VideoSettings(true, VideoQuality.AUTO, VideoQuality.AUTO)
}
}

Expand Down
18 changes: 18 additions & 0 deletions core/src/main/java/org/openedx/core/extension/LongExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.openedx.core.extension

import kotlin.math.log10
import kotlin.math.pow

fun Long.toFileSize(round: Int = 2): String {
try {
if (this <= 0) return "0"
val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
val digitGroups = (log10(this.toDouble()) / log10(1024.0)).toInt()
return String.format(
"%." + round + "f", this / 1024.0.pow(digitGroups.toDouble())
) + " " + units[digitGroups]
} catch (e: Exception) {
println(e.toString())
}
return ""
}
26 changes: 21 additions & 5 deletions core/src/main/java/org/openedx/core/module/DownloadWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
import org.openedx.core.R
import org.openedx.core.module.db.DownloadDao
Expand All @@ -19,19 +21,22 @@ import org.openedx.core.module.db.DownloadModelEntity
import org.openedx.core.module.db.DownloadedState
import org.openedx.core.module.download.CurrentProgress
import org.openedx.core.module.download.FileDownloader
import org.openedx.core.system.notifier.DownloadNotifier
import org.openedx.core.system.notifier.DownloadProgressChanged
import java.io.File

class DownloadWorker(
val context: Context,
parameters: WorkerParameters
) : CoroutineWorker(context, parameters) {
) : CoroutineWorker(context, parameters), CoroutineScope {

private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager

private val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)

private val notifier by inject<DownloadNotifier>(DownloadNotifier::class.java)
private val downloadDao: DownloadDao by inject(DownloadDao::class.java)

private var downloadEnqueue = listOf<DownloadModel>()
Expand All @@ -43,6 +48,7 @@ class DownloadWorker(
)

private var currentDownload: DownloadModel? = null
private var lastUpdateTime = 0L

private val fileDownloader by inject<FileDownloader>(FileDownloader::class.java)

Expand Down Expand Up @@ -79,14 +85,24 @@ class DownloadWorker(

private fun updateProgress() {
fileDownloader.progressListener = object : CurrentProgress {
override fun progress(value: Long) {
if (!fileDownloader.isCanceled) {
override fun progress(value: Long, size: Long) {
val progress = 100 * value / size
// Update no more than 5 times per sec
if (!fileDownloader.isCanceled &&
(System.currentTimeMillis() - lastUpdateTime > 200)
) {
lastUpdateTime = System.currentTimeMillis()

currentDownload?.let {
launch {
notifier.send(DownloadProgressChanged(it.id, value, size))
}

notificationManager.notify(
NOTIFICATION_ID,
notificationBuilder
.setSmallIcon(R.drawable.core_ic_check_in_box)
.setProgress(100, value.toInt(), false)
.setProgress(100, progress.toInt(), false)
.setPriority(NotificationManager.IMPORTANCE_LOW)
.setContentText(context.getString(R.string.core_downloading_in_progress))
.setContentTitle(it.title)
Expand Down Expand Up @@ -152,4 +168,4 @@ class DownloadWorker(
private const val NOTIFICATION_ID = 10
}

}
}
Loading

0 comments on commit 8f6aaf4

Please sign in to comment.