Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic UI to download a file #51

Merged
merged 6 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal class OmhStorageTaskImpl<T>(private val task: suspend () -> OmhResult<T
}

private suspend fun executeFailure(e: Exception) = withContext(Dispatchers.Main) {
onFailure?.invoke(e)
onFailure?.invoke(e).also { e.printStackTrace() }
}

private suspend fun executeSuccess(data: T) {
Expand Down
3 changes: 3 additions & 0 deletions storage-sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
android:name=".AndroidApplication"
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.omh.android.storage.sample.presentation.file_viewer

import android.Manifest
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.Menu
Expand All @@ -15,6 +17,7 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
Expand Down Expand Up @@ -43,6 +46,7 @@ class FileViewerActivity :
private var filesAdapter: FileAdapter? = null
private lateinit var onBackPressedCallback: OnBackPressedCallback
private lateinit var filePicker: ActivityResultLauncher<String>
private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -63,6 +67,25 @@ class FileViewerActivity :
showUploadFileDialog(validUri)
}
}

requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions: Map<String, Boolean> ->
val deniedPermissions = mutableListOf<String>()

permissions.entries.forEach { entry: Map.Entry<String, Boolean> ->
val permission = entry.key
val isGranted = entry.value

if (!isGranted) {
deniedPermissions.add(permission)
}
}

if (deniedPermissions.isNotEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be an early return

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think in this particular case an early return will not make difference

The result code would be something like:

if (deniedPermissions.isEmpty()) {
     return
}

requestPermissions()

which is basically the same, but longer.

What do you think? @Anwera64

requestPermissions()
}
}
}

override fun onResume() {
Expand Down Expand Up @@ -104,6 +127,7 @@ class FileViewerActivity :
is FileViewerViewState.Content -> buildContentState(state)
is FileViewerViewState.SwapLayoutManager -> buildSwapLayoutManagerState()
FileViewerViewState.Finish -> buildFinishState()
FileViewerViewState.CheckPermissions -> requestPermissions()
}

private fun buildInitialState() {
Expand Down Expand Up @@ -276,4 +300,21 @@ class FileViewerActivity :

dialog.dismiss()
}

private fun requestPermissions() {
val permissionsToRequest: Array<String> = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)

val permissionsToAsk: Array<String> = permissionsToRequest.filter { permission: String ->
ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
}.toTypedArray()

if (permissionsToAsk.isNotEmpty()) {
requestPermissionLauncher.launch(permissionsToAsk)
} else {
dispatchEvent(FileViewerViewEvent.DownloadFile)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ sealed class FileViewerViewEvent : ViewEvent {
override fun getEventName() = "FileViewerViewEvent.FileClicked"
}

object DownloadFile : FileViewerViewEvent() {

override fun getEventName() = "FileViewerViewEvent.DownloadFile"
}

object BackPressed : FileViewerViewEvent() {

override fun getEventName() = "FileViewerViewEvent.BackPressed"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package com.omh.android.storage.sample.presentation.file_viewer
import android.content.Context
import android.net.Uri
import com.omh.android.auth.api.OmhAuthClient
import android.os.Environment
import com.omh.android.storage.api.OmhStorageClient
import com.omh.android.storage.api.domain.model.OmhFile
import com.omh.android.storage.api.domain.model.OmhFileType
import com.omh.android.storage.api.domain.usecase.DownloadFileUseCaseResult
import com.omh.android.storage.sample.domain.model.FileType
import com.omh.android.storage.sample.presentation.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.File
import java.io.FileOutputStream
import java.util.Stack
import javax.inject.Inject

Expand All @@ -36,6 +39,7 @@ class FileViewerViewModel @Inject constructor(
var isGridLayoutManager = true
var createFileSelectedType: OmhFileType? = null
private val parentIdStack = Stack<String>().apply { push(ID_ROOT) }
private var lastFileClicked: OmhFile? = null

override fun getInitialState(): FileViewerViewState = FileViewerViewState.Initial

Expand All @@ -50,6 +54,7 @@ class FileViewerViewModel @Inject constructor(
is FileViewerViewEvent.DeleteFile -> deleteFileEvent(event)
is FileViewerViewEvent.UploadFile -> uploadFile(event)
FileViewerViewEvent.SignOut -> signOut()
FileViewerViewEvent.DownloadFile -> downloadFileEvent()
}
}

Expand Down Expand Up @@ -88,7 +93,8 @@ class FileViewerViewModel @Inject constructor(
parentIdStack.push(fileId)
refreshFileListEvent()
} else {
// TODO: Implement download file
lastFileClicked = file
setState(FileViewerViewState.CheckPermissions)
}
}

Expand All @@ -101,6 +107,28 @@ class FileViewerViewModel @Inject constructor(
}
}

private fun downloadFileEvent() {
setState(FileViewerViewState.Loading)

lastFileClicked?.let { file ->
val cancellable = omhStorageClient.downloadFile(file.id)
.addOnSuccess { data ->
handleDownloadSuccess(data, file)
toastMessage.postValue("${file.name} was successfully downloaded")
refreshFileListEvent()
}
.addOnFailure {
toastMessage.postValue("ERROR: ${file.name} was NOT downloaded")
HectorNarvaez marked this conversation as resolved.
Show resolved Hide resolved
refreshFileListEvent()
}
.execute()
cancellableCollector.addCancellable(cancellable)
} ?: run {
toastMessage.postValue("The file was NOT downloaded")
refreshFileListEvent()
}
}

private fun createFileEvent(event: FileViewerViewEvent.CreateFile) {
setState(FileViewerViewState.Loading)
val parentId = parentIdStack.peek()
Expand Down Expand Up @@ -196,4 +224,19 @@ class FileViewerViewModel @Inject constructor(

cancellableCollector.addCancellable(cancellable)
}

private fun handleDownloadSuccess(
result: DownloadFileUseCaseResult,
file: OmhFile
) {
val bytes = result.outputStream
val downloadFolder = Environment
.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
)
val fileToSave = File(downloadFolder, file.name)
val fileOutputStream = FileOutputStream(fileToSave)

bytes.writeTo(fileOutputStream)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ sealed class FileViewerViewState : ViewState {

object Finish : FileViewerViewState() {

override fun getName() = "Finish.SwapLayoutManager"
override fun getName() = "FileViewerViewState.Finish"
}

object CheckPermissions : FileViewerViewState() {

override fun getName() = "FileViewerViewState.CheckPermissions"
}
}