diff --git a/app/src/main/java/com/materiiapps/gloom/di/modules/ManagerModule.kt b/app/src/main/java/com/materiiapps/gloom/di/modules/ManagerModule.kt index 548a326..6eba24b 100644 --- a/app/src/main/java/com/materiiapps/gloom/di/modules/ManagerModule.kt +++ b/app/src/main/java/com/materiiapps/gloom/di/modules/ManagerModule.kt @@ -1,6 +1,7 @@ package com.materiiapps.gloom.di.modules import com.materiiapps.gloom.domain.manager.AuthManager +import com.materiiapps.gloom.domain.manager.DownloadManager import com.materiiapps.gloom.domain.manager.PreferenceManager import org.koin.core.module.dsl.singleOf import org.koin.dsl.module @@ -8,7 +9,7 @@ import org.koin.dsl.module fun managerModule() = module { singleOf(::PreferenceManager) - singleOf(::AuthManager) + singleOf(::DownloadManager) } \ No newline at end of file diff --git a/app/src/main/java/com/materiiapps/gloom/domain/manager/DownloadManager.kt b/app/src/main/java/com/materiiapps/gloom/domain/manager/DownloadManager.kt new file mode 100644 index 0000000..e5a0835 --- /dev/null +++ b/app/src/main/java/com/materiiapps/gloom/domain/manager/DownloadManager.kt @@ -0,0 +1,107 @@ +package com.materiiapps.gloom.domain.manager + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.app.DownloadManager as AndroidDownloadManager +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Environment +import android.os.Handler +import android.os.Looper +import androidx.core.content.getSystemService +import androidx.core.net.toUri +import com.materiiapps.gloom.utils.showToast +import io.ktor.http.HttpHeaders +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +class DownloadManager( + private val context: Context, + private val authManager: AuthManager +) { + private val downloadManager = context.getSystemService()!! + private val downloadScope = CoroutineScope(Dispatchers.IO) + private val gloomDownloadFolder = File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS).resolve("Gloom") + + fun download(url: String) { + val name = url.toUri().lastPathSegment!! + downloadScope.launch { + download(url, File(gloomDownloadFolder, name)).also { + Handler(Looper.getMainLooper()).post { + if(it.exists()) context.showToast("Download complete: $name") + } + } + } + } + + @SuppressLint("UnspecifiedRegisterReceiverFlag") + suspend fun download(url: String, out: File): File { + out.parentFile?.mkdirs() + + return suspendCoroutine { continuation -> + val receiver = object : BroadcastReceiver() { + var dlId = 0L + + @SuppressLint("Range") + override fun onReceive(context: Context, intent: Intent) { + val id = intent.getLongExtra(AndroidDownloadManager.EXTRA_DOWNLOAD_ID, -1) + + if(dlId != id) return + + val (status, reason) = AndroidDownloadManager.Query().run { + setFilterById(dlId) + + val cursor = downloadManager.query(this) + .apply { moveToFirst() } + + if (cursor.count == 0) { + -1 to -1 + } else { + val status = cursor.run { getInt(getColumnIndex(AndroidDownloadManager.COLUMN_STATUS)) } + val reason = cursor.run { getInt(getColumnIndex(AndroidDownloadManager.COLUMN_REASON)) } + + status to reason + } + } + + when(status) { + -1 -> { + context.unregisterReceiver(this) + continuation.resumeWithException(Error("Download canceled")) + } + + AndroidDownloadManager.STATUS_SUCCESSFUL -> { + context.unregisterReceiver(this) + continuation.resume(out) + } + + AndroidDownloadManager.STATUS_FAILED -> { + context.unregisterReceiver(this) + continuation.resumeWithException( + Error("Failed to download $url") + ) + } + + else -> {} + } + } + } + + context.registerReceiver(receiver, IntentFilter(AndroidDownloadManager.ACTION_DOWNLOAD_COMPLETE)) + + receiver.dlId = AndroidDownloadManager.Request(url.toUri()).run { + setTitle("Gloom: Downloading ${out.name}") + setNotificationVisibility(AndroidDownloadManager.Request.VISIBILITY_VISIBLE) + setDestinationUri(out.toUri()) + addRequestHeader(HttpHeaders.Authorization, authManager.authToken) + downloadManager.enqueue(this) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/materiiapps/gloom/ui/screens/release/ReleaseScreen.kt b/app/src/main/java/com/materiiapps/gloom/ui/screens/release/ReleaseScreen.kt index 180e800..e0bbe08 100644 --- a/app/src/main/java/com/materiiapps/gloom/ui/screens/release/ReleaseScreen.kt +++ b/app/src/main/java/com/materiiapps/gloom/ui/screens/release/ReleaseScreen.kt @@ -179,7 +179,7 @@ class ReleaseScreen( ReleaseAsset( name = asset.name, size = asset.size, - onDownloadClick = { /*TODO*/ } + onDownloadClick = { viewModel.downloadAsset(asset.downloadUrl) } ) } } diff --git a/app/src/main/java/com/materiiapps/gloom/ui/viewmodels/release/ReleaseViewModel.kt b/app/src/main/java/com/materiiapps/gloom/ui/viewmodels/release/ReleaseViewModel.kt index e960000..2b4f3ad 100644 --- a/app/src/main/java/com/materiiapps/gloom/ui/viewmodels/release/ReleaseViewModel.kt +++ b/app/src/main/java/com/materiiapps/gloom/ui/viewmodels/release/ReleaseViewModel.kt @@ -1,9 +1,12 @@ package com.materiiapps.gloom.ui.viewmodels.release +import android.content.Context import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.net.toUri import cafe.adriel.voyager.core.model.coroutineScope +import com.materiiapps.gloom.domain.manager.DownloadManager import com.materiiapps.gloom.domain.repository.GraphQLRepository import com.materiiapps.gloom.gql.ReleaseDetailsQuery import com.materiiapps.gloom.gql.fragment.ReleaseAssetFragment @@ -11,10 +14,13 @@ import com.materiiapps.gloom.gql.fragment.ReleaseDetails import com.materiiapps.gloom.gql.type.ReactionContent import com.materiiapps.gloom.rest.utils.getOrNull import com.materiiapps.gloom.ui.viewmodels.list.base.BaseListViewModel +import com.materiiapps.gloom.utils.showToast import kotlinx.coroutines.launch class ReleaseViewModel( private val repo: GraphQLRepository, + private val downloadManager: DownloadManager, + private val context: Context, nameAndTag: Triple ) : BaseListViewModel() { @@ -50,4 +56,9 @@ class ReleaseViewModel( } } + fun downloadAsset(url: String) { + context.showToast("Downloading ${url.toUri().lastPathSegment}...") + downloadManager.download(url) + } + } \ No newline at end of file