diff --git a/app/src/main/java/com/khush/sample/MainActivity.kt b/app/src/main/java/com/khush/sample/MainActivity.kt index 72d375f..539770e 100644 --- a/app/src/main/java/com/khush/sample/MainActivity.kt +++ b/app/src/main/java/com/khush/sample/MainActivity.kt @@ -1,14 +1,12 @@ package com.khush.sample -import android.app.NotificationManager -import android.content.Intent +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.annotation.SuppressLint import android.content.pm.PackageManager import android.os.Build import android.os.Bundle -import android.os.Environment -import android.provider.Settings -import android.util.Log import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import com.google.android.material.snackbar.Snackbar import com.khush.sample.databinding.ActivityMainBinding @@ -18,9 +16,20 @@ class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var snackbar: Snackbar + private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { + Toast.makeText(this, "$it", Toast.LENGTH_SHORT).show() + } + + companion object { + private const val TAG = "susTest" + } + + @SuppressLint("NewApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.i("Testing", "RequestId " + intent.extras?.getInt("key_request_id")) + if (checkSelfPermission(WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) + } binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) @@ -32,35 +41,6 @@ class MainActivity : AppCompatActivity() { return } - var permissions = arrayOf() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !getSystemService(NotificationManager::class.java).areNotificationsEnabled()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - permissions = permissions.plus(android.Manifest.permission.POST_NOTIFICATIONS) - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { - snackbar = - Snackbar.make(binding.root, "Allow storage permission", Snackbar.LENGTH_INDEFINITE) - .setAction("Settings") { - val getpermission = Intent() - getpermission.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION - startActivity(getpermission) - snackbar.dismiss() - } - snackbar.show() - } else if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - permissions = permissions.plus(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - } - - if (permissions.isNotEmpty()) { - requestPermissions(permissions, 101) - Toast.makeText(this, "Notification and Storage Permission Required", Toast.LENGTH_SHORT) - .show() - return - } openTestFragment() } diff --git a/app/src/main/java/com/khush/sample/MainFragment.kt b/app/src/main/java/com/khush/sample/MainFragment.kt index 3d2ae31..3a26b13 100644 --- a/app/src/main/java/com/khush/sample/MainFragment.kt +++ b/app/src/main/java/com/khush/sample/MainFragment.kt @@ -3,7 +3,6 @@ package com.khush.sample import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle -import android.os.Environment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -22,6 +21,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator import com.ketch.DownloadModel import com.ketch.Ketch import com.ketch.Status +import com.ketch.DownloadRequest import com.khush.sample.databinding.FragmentMainBinding import com.khush.sample.databinding.ItemFileBinding import kotlinx.coroutines.Dispatchers @@ -45,6 +45,8 @@ class MainFragment : Fragment() { } } + private val downloadDirectory by lazy { requireContext().cacheDir.path } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -66,9 +68,7 @@ class MainFragment : Fragment() { if (file.exists()) { val uri = this@MainFragment.context?.applicationContext?.let { FileProvider.getUriForFile( - it, - it.packageName + ".provider", - file + it, it.packageName + ".provider", file ) } if (uri != null) { @@ -84,9 +84,7 @@ class MainFragment : Fragment() { } } else { Toast.makeText( - this@MainFragment.context, - "Something went wrong", - Toast.LENGTH_SHORT + this@MainFragment.context, "Something went wrong", Toast.LENGTH_SHORT ).show() } } @@ -130,8 +128,7 @@ class MainFragment : Fragment() { LinearLayoutManager(this.context, LinearLayoutManager.VERTICAL, false) fragmentMainBinding.recyclerView.addItemDecoration( DividerItemDecoration( - this.context, - DividerItemDecoration.VERTICAL + this.context, DividerItemDecoration.VERTICAL ) ) @@ -139,7 +136,7 @@ class MainFragment : Fragment() { fragmentMainBinding.bt1.setOnClickListener { ketch.download( url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, + path = downloadDirectory, fileName = "Sample_Video_1.mp4", tag = "Video", metaData = "158" @@ -150,7 +147,7 @@ class MainFragment : Fragment() { fragmentMainBinding.bt2.setOnClickListener { ketch.download( url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, + path = downloadDirectory, fileName = "Sample_Video_2.mp4", tag = "Video", metaData = "169" @@ -161,7 +158,7 @@ class MainFragment : Fragment() { fragmentMainBinding.bt3.setOnClickListener { ketch.download( url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, + path = downloadDirectory, fileName = "Sample_Video_3.mp4", tag = "Video", metaData = "48" @@ -172,7 +169,7 @@ class MainFragment : Fragment() { fragmentMainBinding.bt4.setOnClickListener { ketch.download( url = "https://picsum.photos/200/300", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, + path = downloadDirectory, fileName = "Sample_Image_1.jpg", tag = "Document", metaData = "1" @@ -183,7 +180,7 @@ class MainFragment : Fragment() { fragmentMainBinding.bt5.setOnClickListener { ketch.download( url = "https://sample-videos.com/pdf/Sample-pdf-5mb.pdf", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, + path = downloadDirectory, fileName = "Sample_Pdf_1.pdf", tag = "Document", metaData = "5" @@ -192,47 +189,51 @@ class MainFragment : Fragment() { fragmentMainBinding.bt6.text = "Multiple" fragmentMainBinding.bt6.setOnClickListener { - ketch.download( - url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, - fileName = "Sample_Video_1.mp4", - tag = "Video", - metaData = "158" - ) - ketch.download( - url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, - fileName = "Sample_Video_2.mp4", - tag = "Video", - metaData = "169" - ) - ketch.download( - url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, - fileName = "Sample_Video_3.mp4", - tag = "Video", - metaData = "48" - ) - ketch.download( - url = "https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_30mb.mp4", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, - fileName = "Sample_Video_4.mp4", - tag = "Video", - metaData = "30" - ) - ketch.download( - url = "https://picsum.photos/200/300", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, - fileName = "Sample_Image_1.jpg", - tag = "Document", - metaData = "1" - ) - ketch.download( - url = "https://sample-videos.com/pdf/Sample-pdf-5mb.pdf", - path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, - fileName = "Sample_Pdf_1.pdf", - tag = "Document", - metaData = "5" + ketch.downloadMultiple( + listOf( + DownloadRequest( + url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + path = downloadDirectory, + fileName = "Sample_Video_1.mp4", + tag = "Video", + metaData = "158", + ), + DownloadRequest( + url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + path = downloadDirectory, + fileName = "Sample_Video_2.mp4", + tag = "Video", + metaData = "169", + ), + DownloadRequest( + url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4", + path = downloadDirectory, + fileName = "Sample_Video_3.mp4", + tag = "Video", + metaData = "48", + ), + DownloadRequest( + url = "https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_30mb.mp4", + path = downloadDirectory, + fileName = "Sample_Video_4.mp4", + tag = "Video", + metaData = "30", + ), + DownloadRequest( + url = "https://picsum.photos/200/300", + path = downloadDirectory, + fileName = "Sample_Image_1.jpg", + tag = "Document", + metaData = "1", + ), + DownloadRequest( + url = "https://sample-videos.com/pdf/Sample-pdf-5mb.pdf", + path = downloadDirectory, + fileName = "Sample_Pdf_1.pdf", + tag = "Document", + metaData = "5" + ), + ) ) } } diff --git a/ketch/src/main/java/com/ketch/DownloadConfig.kt b/ketch/src/main/java/com/ketch/DownloadConfig.kt index 36f0ce8..7334b50 100644 --- a/ketch/src/main/java/com/ketch/DownloadConfig.kt +++ b/ketch/src/main/java/com/ketch/DownloadConfig.kt @@ -6,5 +6,10 @@ import kotlinx.serialization.Serializable @Serializable data class DownloadConfig( val connectTimeOutInMs: Long = DownloadConst.DEFAULT_VALUE_CONNECT_TIMEOUT_MS, - val readTimeOutInMs: Long = DownloadConst.DEFAULT_VALUE_READ_TIMEOUT_MS -) + val readTimeOutInMs: Long = DownloadConst.DEFAULT_VALUE_READ_TIMEOUT_MS, + val maxParallelDownloads: Int = DownloadConst.DEFAULT_VALUE_MAX_PARALLEL_DOWNLOAD, // -1 for infinite parallel download +) { + init { + if (maxParallelDownloads < -1) throw IllegalArgumentException("Max parallel download can't be negative") + } +} diff --git a/ketch/src/main/java/com/ketch/internal/download/DownloadRequest.kt b/ketch/src/main/java/com/ketch/DownloadRequest.kt similarity index 82% rename from ketch/src/main/java/com/ketch/internal/download/DownloadRequest.kt rename to ketch/src/main/java/com/ketch/DownloadRequest.kt index 64d6d9d..69f5ee8 100644 --- a/ketch/src/main/java/com/ketch/internal/download/DownloadRequest.kt +++ b/ketch/src/main/java/com/ketch/DownloadRequest.kt @@ -1,10 +1,10 @@ -package com.ketch.internal.download +package com.ketch import com.ketch.internal.utils.FileUtil.getUniqueId import kotlinx.serialization.Serializable @Serializable -internal data class DownloadRequest( +data class DownloadRequest( val url: String, val path: String, val fileName: String, diff --git a/ketch/src/main/java/com/ketch/Ketch.kt b/ketch/src/main/java/com/ketch/Ketch.kt index 10a2bc1..68285d2 100644 --- a/ketch/src/main/java/com/ketch/Ketch.kt +++ b/ketch/src/main/java/com/ketch/Ketch.kt @@ -5,7 +5,6 @@ import androidx.work.WorkManager import com.ketch.internal.database.DatabaseInstance import com.ketch.internal.download.ApiResponseHeaderChecker import com.ketch.internal.download.DownloadManager -import com.ketch.internal.download.DownloadRequest import com.ketch.internal.network.RetrofitInstance import com.ketch.internal.utils.DownloadConst import com.ketch.internal.utils.DownloadLogger @@ -81,7 +80,7 @@ import java.util.concurrent.TimeUnit * @property logger [Logger] implementation to print logs * @constructor Create empty Ketch */ -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "unused") class Ketch private constructor( private val context: Context, private var downloadConfig: DownloadConfig, @@ -205,6 +204,17 @@ class Ketch private constructor( return downloadRequest.id } + fun downloadMultiple(downloadRequests: List) { + downloadRequests.forEach { + it.apply { + require(url.isNotEmpty() && path.isNotEmpty() && fileName.isNotEmpty()) { + "Missing ${if (url.isEmpty()) "url" else if (path.isEmpty()) "path" else "fileName"}" + } + } + } + downloadManager.downloadAsync(downloadRequests) + } + /** * Cancel download with given [id] * diff --git a/ketch/src/main/java/com/ketch/internal/database/DownloadDao.kt b/ketch/src/main/java/com/ketch/internal/database/DownloadDao.kt index fcef201..f890433 100644 --- a/ketch/src/main/java/com/ketch/internal/database/DownloadDao.kt +++ b/ketch/src/main/java/com/ketch/internal/database/DownloadDao.kt @@ -27,6 +27,9 @@ internal interface DownloadDao { @Query("SELECT * FROM downloads ORDER BY timeQueued ASC") fun getAllEntityFlow(): Flow> + @Query("SELECT COUNT(*) FROM downloads WHERE status = 'PROGRESS' or status = 'STARTED'") + fun getInProgressOrStartedEntityCount() : Int + @Query("SELECT * FROM downloads WHERE lastModified <= :timeMillis ORDER BY timeQueued ASC") fun getEntityTillTimeFlow(timeMillis: Long): Flow> @@ -39,6 +42,9 @@ internal interface DownloadDao { @Query("SELECT * FROM downloads ORDER BY timeQueued ASC") suspend fun getAllEntity(): List + @Query("SELECT * FROM downloads WHERE status = 'QUEUED' ORDER BY timeQueued ASC") + suspend fun getAllQueuedEntity(): List + @Query("SELECT * FROM downloads WHERE lastModified <= :timeMillis ORDER BY timeQueued ASC") suspend fun getEntityTillTime(timeMillis: Long): List diff --git a/ketch/src/main/java/com/ketch/internal/download/DownloadManager.kt b/ketch/src/main/java/com/ketch/internal/download/DownloadManager.kt index fc643c8..c9d6f70 100644 --- a/ketch/src/main/java/com/ketch/internal/download/DownloadManager.kt +++ b/ketch/src/main/java/com/ketch/internal/download/DownloadManager.kt @@ -4,11 +4,13 @@ import android.content.Context import androidx.work.Constraints import androidx.work.Data import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo import androidx.work.WorkManager import com.ketch.DownloadConfig import com.ketch.DownloadModel +import com.ketch.DownloadRequest import com.ketch.Logger import com.ketch.NotificationConfig import com.ketch.Status @@ -16,6 +18,7 @@ import com.ketch.internal.database.DownloadDao import com.ketch.internal.database.DownloadEntity import com.ketch.internal.notification.DownloadNotificationManager import com.ketch.internal.utils.DownloadConst +import com.ketch.internal.utils.DownloadConst.VALUE_DISABLE_MAX_PARALLEL_DOWNLOAD import com.ketch.internal.utils.FileUtil.deleteFileIfExists import com.ketch.internal.utils.UserAction import com.ketch.internal.utils.WorkUtil @@ -56,7 +59,6 @@ internal class DownloadManager( init { scope.launch { - // Observe work infos, only for logging purpose workManager.getWorkInfosByTagFlow(DownloadConst.TAG_DOWNLOAD).flowOn(Dispatchers.IO) .collectLatest { workInfos -> for (workInfo in workInfos) { @@ -96,6 +98,7 @@ internal class DownloadManager( } WorkInfo.State.SUCCEEDED -> { + onWorkFinish() val downloadEntity = findDownloadEntityFromUUID(workInfo.id) logger.log( msg = "Download Success. FileName: ${downloadEntity?.fileName}, " + @@ -104,6 +107,7 @@ internal class DownloadManager( } WorkInfo.State.FAILED -> { + onWorkFinish() val downloadEntity = findDownloadEntityFromUUID(workInfo.id) logger.log( msg = "Download Failed. FileName: ${downloadEntity?.fileName}, " + @@ -113,6 +117,7 @@ internal class DownloadManager( } WorkInfo.State.CANCELLED -> { + onWorkFinish() val downloadEntity = findDownloadEntityFromUUID(workInfo.id) if (downloadEntity?.userAction == UserAction.PAUSE.toString()) { logger.log( @@ -127,15 +132,59 @@ internal class DownloadManager( } } - WorkInfo.State.BLOCKED -> {} // no use case + WorkInfo.State.BLOCKED -> { + onWorkFinish() + } // no use case } } } } } - private suspend fun download(downloadRequest: DownloadRequest) { + private suspend fun onWorkFinish() { + if (downloadConfig.maxParallelDownloads != VALUE_DISABLE_MAX_PARALLEL_DOWNLOAD) + checkIfQueuedAnyDownloadsCanBeStarted() + } + + private suspend fun checkIfQueuedAnyDownloadsCanBeStarted() { + if (canDownloadMore()) { + enqueueDownloads( + downloadDao.getAllQueuedEntity().map { it.toDownloadRequest() } + ) + } + } + + private fun canDownloadMore(): Boolean { + val maxParallelDownloads = downloadConfig.maxParallelDownloads + val parallelDownloadEnabled = maxParallelDownloads == VALUE_DISABLE_MAX_PARALLEL_DOWNLOAD + return parallelDownloadEnabled || downloadDao.getInProgressOrStartedEntityCount() < maxParallelDownloads + } + + private fun getAvailableDownloadRequestCount(): Int { + val maxParallelDownloads = downloadConfig.maxParallelDownloads + val parallelDownloadEnabled = maxParallelDownloads != VALUE_DISABLE_MAX_PARALLEL_DOWNLOAD + return if (parallelDownloadEnabled) maxParallelDownloads - downloadDao.getInProgressOrStartedEntityCount() + else 100 + } + + private suspend fun enqueueDownloads(downloadRequests: List) { + val availableDownloadCount = getAvailableDownloadRequestCount() + downloadRequests.forEachIndexed{ index, downloadRequest -> + val downloadWorkRequest = addDownloadToQueue(downloadRequest) + if (index < availableDownloadCount) { + download(downloadRequest, downloadWorkRequest) + } + } + } + + private suspend fun enqueueDownload(downloadRequest: DownloadRequest) { + val downloadWorkRequest = addDownloadToQueue(downloadRequest) + if (canDownloadMore()) { + download(downloadRequest, downloadWorkRequest) + } + } + private suspend fun addDownloadToQueue(downloadRequest: DownloadRequest): OneTimeWorkRequest { val inputDataBuilder = Data.Builder() .putString(DownloadConst.KEY_DOWNLOAD_REQUEST, downloadRequest.toJson()) .putString(DownloadConst.KEY_NOTIFICATION_CONFIG, notificationConfig.toJson()) @@ -196,7 +245,10 @@ internal class DownloadManager( ) deleteFileIfExists(downloadRequest.path, downloadRequest.fileName) } + return downloadWorkRequest + } + private fun download(downloadRequest: DownloadRequest, downloadWorkRequest: OneTimeWorkRequest) { workManager.enqueueUniqueWork( downloadRequest.id.toString(), ExistingWorkPolicy.KEEP, @@ -213,18 +265,36 @@ internal class DownloadManager( lastModified = System.currentTimeMillis() ) ) - download( - DownloadRequest( - url = downloadEntity.url, - path = downloadEntity.path, - fileName = downloadEntity.fileName, - tag = downloadEntity.tag, - id = downloadEntity.id, - headers = WorkUtil.jsonToHashMap(downloadEntity.headersJson), - metaData = downloadEntity.metaData + enqueueDownload( + downloadEntity.toDownloadRequest() + ) + } + } + + private suspend fun resumeMultiple(downloadEntities: List) { + downloadEntities.forEach { downloadEntity -> + downloadDao.update( + downloadEntity.copy( + userAction = UserAction.RESUME.toString(), + lastModified = System.currentTimeMillis() ) ) } + enqueueDownloads( + downloadEntities.map { it.toDownloadRequest() } + ) + } + + private fun DownloadEntity.toDownloadRequest() : DownloadRequest { + return DownloadRequest( + url = url, + path = path, + fileName = fileName, + tag = tag, + id = id, + headers = WorkUtil.jsonToHashMap(headersJson), + metaData = metaData, + ) } private suspend fun cancel(id: Int) { @@ -278,16 +348,8 @@ internal class DownloadManager( lastModified = System.currentTimeMillis() ) ) - download( - DownloadRequest( - url = downloadEntity.url, - path = downloadEntity.path, - fileName = downloadEntity.fileName, - tag = downloadEntity.tag, - id = downloadEntity.id, - headers = WorkUtil.jsonToHashMap(downloadEntity.headersJson), - metaData = downloadEntity.metaData - ) + enqueueDownload( + downloadEntity.toDownloadRequest() ) } } @@ -304,19 +366,13 @@ internal class DownloadManager( fun resumeAsync(tag: String) { scope.launch { - downloadDao.getAllEntity().forEach { - if (it.tag == tag) { - resume(it.id) - } - } + resumeMultiple(downloadDao.getAllEntityByTag(tag)) } } fun resumeAllAsync() { scope.launch { - downloadDao.getAllEntity().forEach { - resume(it.id) - } + resumeMultiple(downloadDao.getAllEntity()) } } @@ -460,7 +516,13 @@ internal class DownloadManager( fun downloadAsync(downloadRequest: DownloadRequest) { scope.launch { - download(downloadRequest) + enqueueDownload(downloadRequest) + } + } + + fun downloadAsync(downloadRequests: List) { + scope.launch { + enqueueDownloads(downloadRequests) } } diff --git a/ketch/src/main/java/com/ketch/internal/utils/DownloadConst.kt b/ketch/src/main/java/com/ketch/internal/utils/DownloadConst.kt index 594db5c..d36b80c 100644 --- a/ketch/src/main/java/com/ketch/internal/utils/DownloadConst.kt +++ b/ketch/src/main/java/com/ketch/internal/utils/DownloadConst.kt @@ -1,6 +1,8 @@ package com.ketch.internal.utils internal object DownloadConst { + const val VALUE_DISABLE_MAX_PARALLEL_DOWNLOAD = -1 + const val DEFAULT_VALUE_MAX_PARALLEL_DOWNLOAD = VALUE_DISABLE_MAX_PARALLEL_DOWNLOAD const val DEFAULT_VALUE_READ_TIMEOUT_MS = 10000L const val DEFAULT_VALUE_CONNECT_TIMEOUT_MS = 10000L const val BASE_URL = "http://localhost/" diff --git a/ketch/src/main/java/com/ketch/internal/utils/WorkUtil.kt b/ketch/src/main/java/com/ketch/internal/utils/WorkUtil.kt index 7c04752..945480f 100644 --- a/ketch/src/main/java/com/ketch/internal/utils/WorkUtil.kt +++ b/ketch/src/main/java/com/ketch/internal/utils/WorkUtil.kt @@ -3,7 +3,7 @@ package com.ketch.internal.utils import android.content.Context import androidx.core.app.NotificationManagerCompat import com.ketch.NotificationConfig -import com.ketch.internal.download.DownloadRequest +import com.ketch.DownloadRequest import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json