From 5fc8fb9834deea84596d27369cd3ddee9749cdbe Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 15 Feb 2024 15:42:34 +0100 Subject: [PATCH] [ANDROAPP-5857] image download implementation (#3498) * download image files to external directory Signed-off-by: Pablo * download qr/barcode files to external directory Signed-off-by: Pablo * show message when file is downloaded Signed-off-by: Pablo --------- Signed-off-by: Pablo --- .../org/dhis2/commons/bindings/Permissions.kt | 11 ++++ .../org/dhis2/commons/data/FileHandler.kt | 52 +++++++++++++++++++ .../imagedetail/ImageDetailActivity.kt | 34 ++++++++---- commons/src/main/res/values/strings.xml | 2 + .../main/java/org/dhis2/form/ui/FormView.kt | 27 +++++----- .../form/ui/dialog/QRDetailBottomDialog.kt | 38 +++++++++----- 6 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 commons/src/main/java/org/dhis2/commons/bindings/Permissions.kt create mode 100644 commons/src/main/java/org/dhis2/commons/data/FileHandler.kt diff --git a/commons/src/main/java/org/dhis2/commons/bindings/Permissions.kt b/commons/src/main/java/org/dhis2/commons/bindings/Permissions.kt new file mode 100644 index 0000000000..a8619662c1 --- /dev/null +++ b/commons/src/main/java/org/dhis2/commons/bindings/Permissions.kt @@ -0,0 +1,11 @@ +package org.dhis2.commons.bindings + +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat + +fun Context.hasPermissions(permissions: Array): Boolean { + return permissions.all { permission -> + ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED + } +} diff --git a/commons/src/main/java/org/dhis2/commons/data/FileHandler.kt b/commons/src/main/java/org/dhis2/commons/data/FileHandler.kt new file mode 100644 index 0000000000..4be787d393 --- /dev/null +++ b/commons/src/main/java/org/dhis2/commons/data/FileHandler.kt @@ -0,0 +1,52 @@ +package org.dhis2.commons.data + +import android.graphics.Bitmap +import android.os.Environment +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import java.io.File +import java.io.FileOutputStream + +class FileHandler { + + private val destinationResult = MutableLiveData() + + fun saveBitmapAndOpen( + bitmap: Bitmap, + outputFileName: String, + fileCallback: (LiveData) -> Unit, + ) { + fileCallback(destinationResult) + + val imagesFolder = getDownloadDirectory(outputFileName) + destinationResult.value = saveBitmapAndOpen(bitmap, imagesFolder) + } + + fun copyAndOpen( + sourceFile: File, + fileCallback: (LiveData) -> Unit, + ) { + fileCallback(destinationResult) + + val imagesFolder = getDownloadDirectory(sourceFile.name) + destinationResult.value = copyFile(sourceFile, imagesFolder) + } + + private fun saveBitmapAndOpen(bitmap: Bitmap, destinationFolder: File): File { + val os = FileOutputStream(destinationFolder) + bitmap.compress(Bitmap.CompressFormat.PNG, 100, os) + os.close() + return destinationFolder + } + + private fun copyFile(sourceFile: File, destinationDirectory: File): File { + return sourceFile.copyTo(destinationDirectory, true) + } + + private fun getDownloadDirectory(outputFileName: String) = File( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS, + ), + "dhis2" + File.separator + outputFileName, + ) +} diff --git a/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt b/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt index 5d0a0e9594..f624a2bc5e 100644 --- a/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt +++ b/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt @@ -3,6 +3,7 @@ package org.dhis2.commons.dialogs.imagedetail import android.content.Context import android.content.Intent import android.os.Bundle +import android.widget.Toast import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.remember @@ -10,6 +11,7 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.core.content.FileProvider import org.dhis2.commons.R +import org.dhis2.commons.data.FileHandler import org.dhis2.commons.data.FormFileProvider import org.dhis2.commons.extensions.getBitmap import org.hisp.dhis.mobile.ui.designsystem.component.FullScreenImage @@ -32,6 +34,8 @@ class ImageDetailActivity : AppCompatActivity() { } } + private val fileHandler = FileHandler() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val title = intent.getStringExtra(ARG_IMAGE_TITLE) @@ -46,14 +50,26 @@ class ImageDetailActivity : AppCompatActivity() { painter = painter!!, title = title.orEmpty(), onDismiss = { finish() }, - onDownloadButtonClick = { }, + onDownloadButtonClick = { + fileHandler.copyAndOpen( + File(imagePath), + ) { file -> + file.observe(this) { + Toast.makeText( + this, + getString(R.string.file_downladed), + Toast.LENGTH_SHORT, + ).show() + } + } + }, onShareButtonClick = { shareImage(imagePath) }, ) } } private fun shareImage(image: String) { - val intent = Intent(Intent.ACTION_SEND).apply { + with(Intent(Intent.ACTION_SEND)) { val contentUri = FileProvider.getUriForFile( this@ImageDetailActivity, FormFileProvider.fileProviderAuthority, @@ -62,14 +78,14 @@ class ImageDetailActivity : AppCompatActivity() { setDataAndType(contentUri, contentResolver.getType(contentUri)) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) putExtra(Intent.EXTRA_STREAM, contentUri) - } - val title = resources.getString(R.string.open_with) - val chooser = Intent.createChooser(intent, title) - try { - startActivity(chooser) - } catch (e: IOException) { - Timber.e(e) + val title = resources.getString(R.string.open_with) + val chooser = Intent.createChooser(intent, title) + try { + startActivity(chooser) + } catch (e: IOException) { + Timber.e(e) + } } } } diff --git a/commons/src/main/res/values/strings.xml b/commons/src/main/res/values/strings.xml index 4aaa25beca..1b21f6fdfe 100644 --- a/commons/src/main/res/values/strings.xml +++ b/commons/src/main/res/values/strings.xml @@ -236,6 +236,8 @@ Open with + File downladed successfully + diff --git a/form/src/main/java/org/dhis2/form/ui/FormView.kt b/form/src/main/java/org/dhis2/form/ui/FormView.kt index c63c8a9799..dbb727030c 100644 --- a/form/src/main/java/org/dhis2/form/ui/FormView.kt +++ b/form/src/main/java/org/dhis2/form/ui/FormView.kt @@ -50,6 +50,7 @@ import org.dhis2.commons.Constants import org.dhis2.commons.bindings.getFileFrom import org.dhis2.commons.bindings.getFileFromGallery import org.dhis2.commons.bindings.rotateImage +import org.dhis2.commons.data.FileHandler import org.dhis2.commons.data.FormFileProvider import org.dhis2.commons.dialogs.AlertBottomDialog import org.dhis2.commons.dialogs.CustomDialog @@ -324,6 +325,8 @@ class FormView : Fragment() { Manifest.permission.READ_MEDIA_VIDEO, ) + private val fileHandler = FileHandler() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -1004,18 +1007,18 @@ class FormView : Fragment() { } private fun openFile(event: RecyclerViewUiEvents.OpenFile) { - event.field.displayName?.let { filePath -> - val file = File(filePath) - val fileUri = FileProvider.getUriForFile( - requireContext(), - FormFileProvider.fileProviderAuthority, - file, - ) - startActivity( - Intent(Intent.ACTION_VIEW) - .setDataAndType(fileUri, "*/*") - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION), - ) + activity?.activityResultRegistry?.let { + event.field.displayName?.let { filePath -> + fileHandler.copyAndOpen(File(filePath)) { file -> + file.observe(viewLifecycleOwner) { + Toast.makeText( + requireContext(), + getString(R.string.file_downladed), + Toast.LENGTH_SHORT, + ).show() + } + } + } } } diff --git a/form/src/main/java/org/dhis2/form/ui/dialog/QRDetailBottomDialog.kt b/form/src/main/java/org/dhis2/form/ui/dialog/QRDetailBottomDialog.kt index ba11ce614c..5095aaa05b 100644 --- a/form/src/main/java/org/dhis2/form/ui/dialog/QRDetailBottomDialog.kt +++ b/form/src/main/java/org/dhis2/form/ui/dialog/QRDetailBottomDialog.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.material.Icon @@ -27,6 +28,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.FileProvider import androidx.fragment.app.viewModels import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.dhis2.commons.data.FileHandler import org.dhis2.commons.data.FormFileProvider import org.dhis2.commons.resources.ColorType import org.dhis2.commons.resources.ColorUtils @@ -67,6 +69,8 @@ QRDetailBottomDialog( } private var showBottomSheet: Boolean = true + private val fileHandler = FileHandler() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme) @@ -127,10 +131,12 @@ QRDetailBottomDialog( Row(horizontalArrangement = Arrangement.Center) { when (renderingType) { UiRenderType.QR_CODE, UiRenderType.GS1_DATAMATRIX -> { - val isGS1Matrix = value.startsWith(GS1Elements.GS1_d2_IDENTIFIER.element) + val isGS1Matrix = + value.startsWith(GS1Elements.GS1_d2_IDENTIFIER.element) val content = formattedContent(value) QrCodeBlock(data = content, isDataMatrix = isGS1Matrix) } + else -> { BarcodeBlock(data = value) } @@ -185,7 +191,12 @@ QRDetailBottomDialog( addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) setDataAndType(uri, context?.contentResolver?.getType(uri)) putExtra(Intent.EXTRA_STREAM, uri) - startActivity(Intent.createChooser(this, context?.getString(R.string.share))) + startActivity( + Intent.createChooser( + this, + context?.getString(R.string.share), + ), + ) } } }, @@ -202,16 +213,19 @@ QRDetailBottomDialog( enabled = true, text = resources.getString(R.string.download), onClick = { - qrContentUri?.let { uri -> - startActivity( - Intent().apply { - action = Intent.ACTION_VIEW - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - setDataAndType(uri, context?.contentResolver?.getType(uri)) - putExtra(Intent.EXTRA_STREAM, uri) - }, - ) - // implement download action here + viewModel.qrBitmap.value?.onSuccess { bitmap -> + fileHandler.saveBitmapAndOpen( + bitmap, + "$label.png", + ) { file -> + file.observe(viewLifecycleOwner) { + Toast.makeText( + requireContext(), + getString(R.string.file_downladed), + Toast.LENGTH_SHORT, + ).show() + } + } } }, ),