diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 626bba40df..3f51404e47 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,9 +16,6 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
-
-
-
bitmap
}
- return File(
- FileResourceDirectoryHelper.getFileResourceDirectory(context),
+ val file = File(
+ FileResourceDirectoryHelper.getFileCacheResourceDirectory(context),
"tempFile.png",
- ).apply { writeBitmap(bitmap, Bitmap.CompressFormat.JPEG, 100) }
+ )
+
+ file.writeBitmap(bitmap, Bitmap.CompressFormat.JPEG, 100)
+
+ return file
+}
+
+fun Bitmap.rotateImageAndSave(context: Context): File {
+ val file = File(
+ FileResourceDirectoryHelper.getFileCacheResourceDirectory(context),
+ "tempFile.png",
+ )
+
+ file.writeBitmap(this, Bitmap.CompressFormat.JPEG, 100)
+
+ val ei = ExifInterface(file.path)
+
+ val orientation =
+ ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
+
+ val bitmap = when (orientation) {
+ ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(this, 90F)
+ ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(this, 180F)
+ ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(this, 270F)
+ else -> this
+ }
+
+ file.writeBitmap(bitmap!!, Bitmap.CompressFormat.JPEG, 100)
+
+ return file
}
private fun rotateImage(source: Bitmap, angle: Float): Bitmap? {
@@ -107,9 +136,11 @@ private fun getFilePath(context: Context, uri: Uri): String? {
split[splitIndex].toLong(),
)
}
+
isExternalStorageDocument(copy) -> {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
+
isMediaDocument(copy) -> {
copy = when (split[0]) {
"image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
diff --git a/form/src/main/java/org/dhis2/form/model/UiEventType.kt b/form/src/main/java/org/dhis2/form/model/UiEventType.kt
index 81eb03f23b..65b1875a7b 100644
--- a/form/src/main/java/org/dhis2/form/model/UiEventType.kt
+++ b/form/src/main/java/org/dhis2/form/model/UiEventType.kt
@@ -2,8 +2,6 @@ package org.dhis2.form.model
enum class UiEventType {
REQUEST_LOCATION_BY_MAP,
- ADD_PICTURE,
- ADD_FILE,
OPEN_FILE,
SHARE_IMAGE,
}
diff --git a/form/src/main/java/org/dhis2/form/ui/Form.kt b/form/src/main/java/org/dhis2/form/ui/Form.kt
index d030e1d1df..5fc6925e94 100644
--- a/form/src/main/java/org/dhis2/form/ui/Form.kt
+++ b/form/src/main/java/org/dhis2/form/ui/Form.kt
@@ -147,6 +147,15 @@ fun Form(
onNextSection,
)
},
+ onFileSelected = { path ->
+ intentHandler.invoke(
+ FormIntent.OnStoreFile(
+ uid = fieldUiModel.uid,
+ filePath = path,
+ valueType = fieldUiModel.valueType,
+ ),
+ )
+ },
)
}
}
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 f3f4b34681..6f68c2946e 100644
--- a/form/src/main/java/org/dhis2/form/ui/FormView.kt
+++ b/form/src/main/java/org/dhis2/form/ui/FormView.kt
@@ -3,9 +3,7 @@ package org.dhis2.form.ui
import android.Manifest
import android.app.Activity.RESULT_OK
import android.content.ActivityNotFoundException
-import android.content.DialogInterface
import android.content.Intent
-import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -15,7 +13,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -28,12 +25,7 @@ import androidx.fragment.app.viewModels
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.journeyapps.barcodescanner.ScanOptions
-import org.dhis2.commons.ActivityResultObservable
-import org.dhis2.commons.ActivityResultObserver
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.date.DateUtils
@@ -77,7 +69,6 @@ import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialog
import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialogUiModel
import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle
import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue
-import org.hisp.dhis.android.core.arch.helpers.FileResourceDirectoryHelper
import org.hisp.dhis.android.core.common.ValueType
import org.hisp.dhis.android.core.common.ValueTypeRenderingType
import org.hisp.dhis.android.core.event.EventStatus
@@ -131,91 +122,6 @@ class FormView : Fragment() {
}
}
- private val requestCameraPermissions =
- registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
- if (it.values.all { isGranted -> isGranted }) {
- showAddImageOptions()
- (context as ActivityResultObservable?)?.subscribe(object : ActivityResultObserver {
- override fun onActivityResult(
- requestCode: Int,
- resultCode: Int,
- data: Intent?,
- ) {
- if (resultCode != RESULT_OK) {
- showAddImageOptions()
- }
- }
-
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray,
- ) {
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- showAddImageOptions()
- }
- }
- })
- } else {
- Toast.makeText(
- requireContext(),
- requireContext().getString(R.string.camera_permission_denied),
- Toast.LENGTH_LONG,
- ).show()
- }
- }
-
- private val takePicture =
- registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
- if (success) {
- val imageFile = File(
- FileResourceDirectoryHelper.getFileResourceDirectory(requireContext()),
- TEMP_FILE,
- ).rotateImage(requireContext())
- onSavePicture?.invoke(imageFile.path)
-
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- } else {
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- }
- }
-
- private val pickImage =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
- if (activityResult.resultCode == RESULT_OK) {
- getFileFromGallery(requireContext(), activityResult.data?.data)?.also { file ->
- onSavePicture?.invoke(file.path)
- }
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- } else {
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- }
- }
-
- private val pickFile =
- registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
- if (uri != null) {
- getFileFrom(requireContext(), uri)?.also { file ->
- onSavePicture?.invoke(file.path)
- }
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- } else {
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- }
- }
-
private val requestStoragePermissions =
registerForActivityResult(
ActivityResultContracts.RequestPermission(),
@@ -245,21 +151,6 @@ class FormView : Fragment() {
private lateinit var formSectionMapper: FormSectionMapper
var scrollCallback: ((Boolean) -> Unit)? = null
private var displayConfErrors = true
- private var onSavePicture: ((String) -> Unit)? = null
-
- private val storagePermissions = arrayOf(
- Manifest.permission.CAMERA,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- )
-
- @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
- private val storagePermissions33 = arrayOf(
- Manifest.permission.CAMERA,
- Manifest.permission.READ_MEDIA_IMAGES,
- Manifest.permission.READ_MEDIA_AUDIO,
- Manifest.permission.READ_MEDIA_VIDEO,
- )
private val fileHandler = FileHandler()
@@ -553,9 +444,7 @@ class FormView : Fragment() {
is RecyclerViewUiEvents.DisplayQRCode -> displayQRImage(uiEvent)
is RecyclerViewUiEvents.ScanQRCode -> requestQRScan(uiEvent)
is RecyclerViewUiEvents.OpenOrgUnitDialog -> showOrgUnitDialog(uiEvent)
- is RecyclerViewUiEvents.AddImage -> requestAddImage(uiEvent)
is RecyclerViewUiEvents.OpenFile -> openFile(uiEvent)
- is RecyclerViewUiEvents.OpenFileSelector -> openFileSelector(uiEvent)
is RecyclerViewUiEvents.OpenChooserIntent -> openChooserIntent(uiEvent)
is RecyclerViewUiEvents.SelectPeriod -> showPeriodDialog(uiEvent)
}
@@ -699,84 +588,6 @@ class FormView : Fragment() {
)
}
- private fun requestAddImage(event: RecyclerViewUiEvents.AddImage) {
- onSavePicture = { picture ->
- intentHandler(
- FormIntent.OnStoreFile(
- event.uid,
- picture,
- ValueType.IMAGE,
- ),
- )
- }
- requestCameraPermissions.launch(permissions())
- }
-
- private fun permissions() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- storagePermissions33
- } else {
- storagePermissions
- }
-
- private fun showAddImageOptions() {
- val options = arrayOf(
- requireContext().getString(R.string.take_photo),
- requireContext().getString(R.string.from_gallery),
- requireContext().getString(R.string.cancel),
- )
- MaterialAlertDialogBuilder(requireActivity(), R.style.MaterialDialog)
- .setTitle(requireContext().getString(R.string.select_option))
- .setOnCancelListener {
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- }
- .setItems(options) { dialog: DialogInterface, item: Int ->
- run {
- when (options[item]) {
- requireContext().getString(R.string.take_photo) -> {
- val photoUri = FileProvider.getUriForFile(
- requireContext(),
- FormFileProvider.fileProviderAuthority,
- File(
- FileResourceDirectoryHelper.getFileResourceDirectory(
- requireContext(),
- ),
- TEMP_FILE,
- ),
- )
- takePicture.launch(photoUri)
- }
-
- requireContext().getString(R.string.from_gallery) -> {
- pickImage.launch(Intent(Intent.ACTION_PICK).apply { type = "image/*" })
- }
-
- requireContext().getString(R.string.cancel) -> {
- viewModel.getFocusedItemUid()?.let {
- viewModel.submitIntent(FormIntent.OnAddImageFinished(it))
- }
- }
- }
- dialog.dismiss()
- }
- }
- .show()
- }
-
- private fun openFileSelector(event: RecyclerViewUiEvents.OpenFileSelector) {
- onSavePicture = { file ->
- intentHandler(
- FormIntent.OnStoreFile(
- event.field.uid,
- file,
- event.field.valueType,
- ),
- )
- }
- pickFile.launch("*/*")
- }
-
private fun openFile(event: RecyclerViewUiEvents.OpenFile) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
downloadFile(event.field.displayName)
diff --git a/form/src/main/java/org/dhis2/form/ui/dialog/ImagePickerOptionsDialog.kt b/form/src/main/java/org/dhis2/form/ui/dialog/ImagePickerOptionsDialog.kt
new file mode 100644
index 0000000000..d376ea44cd
--- /dev/null
+++ b/form/src/main/java/org/dhis2/form/ui/dialog/ImagePickerOptionsDialog.kt
@@ -0,0 +1,83 @@
+package org.dhis2.form.ui.dialog
+
+import android.content.Context
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CameraAlt
+import androidx.compose.material.icons.outlined.Collections
+import androidx.compose.material.icons.outlined.Image
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import org.dhis2.form.R
+import org.hisp.dhis.mobile.ui.designsystem.component.BottomSheetShell
+import org.hisp.dhis.mobile.ui.designsystem.component.ButtonCarousel
+import org.hisp.dhis.mobile.ui.designsystem.component.CarouselButtonData
+import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor
+import org.hisp.dhis.mobile.ui.designsystem.theme.TextColor
+
+@Composable
+internal fun ImagePickerOptionsDialog(
+ title: String,
+ showImageOptions: Boolean,
+ onDismiss: () -> Unit,
+ onTakePicture: (Context) -> Unit,
+ onSelectFromGallery: () -> Unit,
+) {
+ AnimatedVisibility(
+ visible = showImageOptions,
+ enter = slideInVertically() + fadeIn(),
+ exit = slideOutVertically() + fadeOut(),
+ ) {
+ BottomSheetShell(
+ title = title,
+ icon = {
+ Icon(Icons.Outlined.Image, contentDescription = null, tint = SurfaceColor.Primary)
+ },
+ onDismiss = onDismiss,
+ buttonBlock = {
+ val context = LocalContext.current
+ ButtonCarousel(
+ carouselButtonList = listOf(
+ CarouselButtonData(
+ onClick = {
+ onDismiss()
+ onTakePicture(context)
+ },
+ enabled = true,
+ text = stringResource(R.string.take_photo),
+ icon = {
+ Icon(
+ Icons.Outlined.CameraAlt,
+ contentDescription = null,
+ tint = TextColor.OnSurface,
+ )
+ },
+ ),
+ CarouselButtonData(
+ onClick = {
+ onDismiss()
+ onSelectFromGallery()
+ },
+ enabled = true,
+ text = stringResource(R.string.from_gallery_v2),
+ icon = {
+ Icon(
+ Icons.Outlined.Collections,
+ contentDescription = null,
+ tint = TextColor.OnSurface,
+ )
+ },
+ ),
+ ),
+ )
+ },
+ content = null,
+ )
+ }
+}
diff --git a/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt b/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt
index f28f0d830e..eaa7eefeeb 100644
--- a/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt
+++ b/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt
@@ -37,14 +37,6 @@ sealed class RecyclerViewUiEvents {
val orgUnitSelectorScope: OrgUnitSelectorScope?,
) : RecyclerViewUiEvents()
- data class AddImage(
- val uid: String,
- ) : RecyclerViewUiEvents()
-
- data class OpenFileSelector(
- val field: FieldUiModel,
- ) : RecyclerViewUiEvents()
-
data class OpenFile(
val field: FieldUiModel,
) : RecyclerViewUiEvents()
diff --git a/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt b/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt
index f05f27c5aa..3b8c7eb56d 100644
--- a/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt
+++ b/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt
@@ -3,8 +3,6 @@ package org.dhis2.form.ui.event
import android.content.Intent
import org.dhis2.form.model.FieldUiModel
import org.dhis2.form.model.UiEventType
-import org.dhis2.form.model.UiEventType.ADD_FILE
-import org.dhis2.form.model.UiEventType.ADD_PICTURE
import org.dhis2.form.model.UiEventType.OPEN_FILE
import org.dhis2.form.model.UiEventType.REQUEST_LOCATION_BY_MAP
import org.dhis2.form.model.UiEventType.SHARE_IMAGE
@@ -35,9 +33,6 @@ class UiEventFactoryImpl(
featureType = getFeatureType(renderingType),
value = value,
)
-
- ADD_PICTURE -> RecyclerViewUiEvents.AddImage(uid)
- ADD_FILE -> RecyclerViewUiEvents.OpenFileSelector(fieldUiModel)
OPEN_FILE -> RecyclerViewUiEvents.OpenFile(fieldUiModel)
SHARE_IMAGE -> RecyclerViewUiEvents.OpenChooserIntent(
Intent.ACTION_SEND,
diff --git a/form/src/main/java/org/dhis2/form/ui/files/GetFileResource.kt b/form/src/main/java/org/dhis2/form/ui/files/GetFileResource.kt
new file mode 100644
index 0000000000..03c6b3b375
--- /dev/null
+++ b/form/src/main/java/org/dhis2/form/ui/files/GetFileResource.kt
@@ -0,0 +1,134 @@
+package org.dhis2.form.ui.files
+
+import android.app.Activity
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.webkit.MimeTypeMap
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.content.FileProvider
+import org.dhis2.commons.bindings.rotateImage
+import org.dhis2.commons.data.FormFileProvider
+import org.hisp.dhis.android.core.arch.helpers.FileResourceDirectoryHelper
+import java.io.File
+
+private class GetFileResource(
+ private val allowMultipleSelection: Boolean = false,
+) : ActivityResultContract>() {
+ override fun createIntent(context: Context, input: String): Intent {
+ return Intent(Intent.ACTION_GET_CONTENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = input
+ putExtra(Intent.EXTRA_LOCAL_ONLY, true)
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultipleSelection)
+ .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): List {
+ return intent.takeIf {
+ resultCode == Activity.RESULT_OK
+ }?.getClipDataUris() ?: emptyList()
+ }
+}
+
+@Composable
+fun rememberFilePicker(
+ onResult: (String) -> Unit,
+) = with(LocalContext.current) {
+ val launcher = rememberLauncherForActivityResult(
+ contract = GetFileResource(),
+ onResult = { uris ->
+ uris.firstOrNull()?.toFile(context = this)?.path?.let(onResult)
+ },
+ )
+ return@with launcher
+}
+
+@Composable
+fun rememberCameraPicker(
+ onSuccess: (String) -> Unit,
+ onError: () -> Unit,
+ onPermissionAccepted: () -> Unit,
+) = with(
+ LocalContext.current,
+) {
+ val tempFile = File(
+ FileResourceDirectoryHelper.getFileResourceDirectory(this),
+ "tempFile.png",
+ )
+
+ val photoUri = FileProvider.getUriForFile(
+ this,
+ FormFileProvider.fileProviderAuthority,
+ tempFile,
+ )
+
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicture(),
+ onResult = { success ->
+ if (success) {
+ onSuccess(tempFile.rotateImage(this).path)
+ } else {
+ onError()
+ }
+ },
+ )
+ val cameraPermission = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestPermission(),
+ ) { accepted ->
+ if (accepted) {
+ onPermissionAccepted()
+ launcher.launch(photoUri)
+ } else {
+ onError()
+ }
+ }
+
+ return@with Triple(photoUri, launcher, cameraPermission)
+}
+
+internal fun Uri.toFile(context: Context, suffix: String = ""): File? {
+ var resultFile: File? = null
+ if (ContentResolver.SCHEME_CONTENT == this.scheme) {
+ val cr = context.contentResolver
+ val mimeTypeMap = MimeTypeMap.getSingleton()
+ val extensionsFile = mimeTypeMap.getExtensionFromMimeType(cr.getType(this))
+ resultFile = File.createTempFile(
+ "tempFile",
+ "$suffix.$extensionsFile",
+ context.cacheDir,
+ )
+ val input = cr.openInputStream(this)
+ resultFile.outputStream().use { stream ->
+ input?.copyTo(stream)
+ }
+ input?.close()
+ }
+ return resultFile
+}
+
+internal fun Intent.getClipDataUris(): List {
+ val resultSet = LinkedHashSet()
+ data?.let { data ->
+ resultSet.add(data)
+ }
+ val clipData = clipData
+ if (clipData == null && resultSet.isEmpty()) {
+ return emptyList()
+ } else if (clipData != null) {
+ for (i in 0 until clipData.itemCount) {
+ val uri = clipData.getItemAt(i).uri
+ if (uri != null) {
+ resultSet.add(uri)
+ }
+ }
+ }
+ return ArrayList(resultSet)
+}
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
index 2515ddf353..e709a498f6 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
@@ -65,6 +65,7 @@ fun FieldProvider(
resources: ResourceManager,
focusManager: FocusManager,
onNextClicked: () -> Unit,
+ onFileSelected: (String) -> Unit,
) {
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val focusRequester = remember { FocusRequester() }
@@ -136,6 +137,7 @@ fun FieldProvider(
focusRequester = focusRequester,
onNextClicked = onNextClicked,
focusManager = focusManager,
+ onFileSelected = onFileSelected,
)
}
}
@@ -151,6 +153,7 @@ fun ProvideByValueType(
focusRequester: FocusRequester,
onNextClicked: () -> Unit,
focusManager: FocusManager,
+ onFileSelected: (String) -> Unit,
) {
when (fieldUiModel.valueType) {
ValueType.TEXT -> {
@@ -298,6 +301,7 @@ fun ProvideByValueType(
modifier = modifier,
fieldUiModel = fieldUiModel,
resources = resources,
+ onFileSelected = onFileSelected,
uiEventHandler = uiEventHandler,
)
}
@@ -419,6 +423,7 @@ fun ProvideByValueType(
intentHandler = intentHandler,
uiEventHandler = uiEventHandler,
resources = resources,
+ onFileSelected = onFileSelected,
)
}
}
@@ -743,7 +748,12 @@ private fun ProvideIntegerNegative(
)
}
var value by remember(fieldUiModel.value) {
- mutableStateOf(TextFieldValue(fieldUiModel.value?.replace("-", "") ?: "", savedTextSelection))
+ mutableStateOf(
+ TextFieldValue(
+ fieldUiModel.value?.replace("-", "") ?: "",
+ savedTextSelection,
+ ),
+ )
}
InputNegativeInteger(
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/ImageInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/ImageInputProvider.kt
index e1a0e5d67e..a6014a5968 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/ImageInputProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/ImageInputProvider.kt
@@ -1,6 +1,8 @@
package org.dhis2.form.ui.provider.inputfield
+import android.Manifest
import android.content.Intent
+import android.content.pm.PackageManager
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -10,6 +12,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.core.content.ContextCompat
import org.dhis2.commons.extensions.getBitmap
import org.dhis2.commons.resources.ResourceManager
import org.dhis2.form.R
@@ -17,8 +20,10 @@ import org.dhis2.form.extensions.inputState
import org.dhis2.form.extensions.legend
import org.dhis2.form.extensions.supportingText
import org.dhis2.form.model.FieldUiModel
-import org.dhis2.form.model.UiEventType
+import org.dhis2.form.ui.dialog.ImagePickerOptionsDialog
import org.dhis2.form.ui.event.RecyclerViewUiEvents
+import org.dhis2.form.ui.files.rememberCameraPicker
+import org.dhis2.form.ui.files.rememberFilePicker
import org.dhis2.form.ui.intent.FormIntent
import org.hisp.dhis.mobile.ui.designsystem.component.InputImage
import org.hisp.dhis.mobile.ui.designsystem.component.UploadState
@@ -30,10 +35,39 @@ internal fun ProvideInputImage(
resources: ResourceManager,
intentHandler: (FormIntent) -> Unit,
uiEventHandler: (RecyclerViewUiEvents) -> Unit,
+ onFileSelected: (filePath: String) -> Unit,
) {
- var uploadState by remember(fieldUiModel) { mutableStateOf(getUploadState(fieldUiModel.displayName, fieldUiModel.isLoadingData)) }
+ var showImageOptions by remember { mutableStateOf(false) }
+
+ var uploadState by remember(fieldUiModel) {
+ mutableStateOf(
+ getUploadState(
+ fieldUiModel.displayName,
+ fieldUiModel.isLoadingData,
+ ),
+ )
+ }
val painter = fieldUiModel.displayName?.getBitmap()?.let { BitmapPainter(it.asImageBitmap()) }
+
+ val filePicker = rememberFilePicker(onFileSelected)
+
+ val (tempFileUri, imagePicker, cameraPermission) = rememberCameraPicker(
+ onSuccess = { filePath ->
+ onFileSelected(filePath)
+ uploadState = getUploadState(fieldUiModel.displayName, false)
+ },
+ onError = {
+ uploadState = getUploadState(fieldUiModel.displayName, false)
+ intentHandler.invoke(
+ FormIntent.OnAddImageFinished(fieldUiModel.uid),
+ )
+ },
+ onPermissionAccepted = {
+ uploadState = getUploadState(fieldUiModel.displayName, true)
+ },
+ )
+
InputImage(
modifier = modifier.fillMaxWidth(),
title = fieldUiModel.label,
@@ -69,8 +103,28 @@ internal fun ProvideInputImage(
)
},
onAddButtonClicked = {
- uploadState = getUploadState(fieldUiModel.displayName, true)
- fieldUiModel.invokeUiEvent(UiEventType.ADD_PICTURE)
+ showImageOptions = true
+ },
+ )
+
+ ImagePickerOptionsDialog(
+ title = fieldUiModel.label,
+ showImageOptions = showImageOptions,
+ onDismiss = { showImageOptions = false },
+ onTakePicture = { context ->
+ if (ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.CAMERA,
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ uploadState = getUploadState(fieldUiModel.displayName, true)
+ imagePicker.launch(tempFileUri)
+ } else {
+ cameraPermission.launch(Manifest.permission.CAMERA)
+ }
+ },
+ onSelectFromGallery = {
+ filePicker.launch("image/*")
},
)
}
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/InputFileProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/InputFileProvider.kt
index f6f03c16c3..a56369475c 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/InputFileProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/InputFileProvider.kt
@@ -13,8 +13,8 @@ import org.dhis2.form.extensions.inputState
import org.dhis2.form.extensions.legend
import org.dhis2.form.extensions.supportingText
import org.dhis2.form.model.FieldUiModel
-import org.dhis2.form.model.UiEventType
import org.dhis2.form.ui.event.RecyclerViewUiEvents
+import org.dhis2.form.ui.files.rememberFilePicker
import org.hisp.dhis.mobile.ui.designsystem.component.InputFileResource
import org.hisp.dhis.mobile.ui.designsystem.component.UploadFileState
import java.io.File
@@ -25,6 +25,7 @@ internal fun ProvideInputFileResource(
modifier: Modifier,
fieldUiModel: FieldUiModel,
resources: ResourceManager,
+ onFileSelected: (filePath: String) -> Unit,
uiEventHandler: (RecyclerViewUiEvents) -> Unit,
) {
var uploadState by remember(fieldUiModel) {
@@ -37,6 +38,8 @@ internal fun ProvideInputFileResource(
}
val file = fieldUiModel.displayName?.let { File(it) }
+ val filePicker = rememberFilePicker(onFileSelected)
+
InputFileResource(
modifier = modifier.fillMaxWidth(),
title = fieldUiModel.label,
@@ -48,7 +51,7 @@ internal fun ProvideInputFileResource(
fileWeight = file?.length()?.let { fileSizeLabel(it) },
onSelectFile = {
uploadState = getFileUploadState(fieldUiModel.displayName, true)
- fieldUiModel.invokeUiEvent(UiEventType.ADD_FILE)
+ filePicker.launch("*/*")
},
onClear = { fieldUiModel.onClear() },
onUploadFile = {
diff --git a/form/src/main/res/values/strings.xml b/form/src/main/res/values/strings.xml
index c64d2249ac..28d8b9d85f 100644
--- a/form/src/main/res/values/strings.xml
+++ b/form/src/main/res/values/strings.xml
@@ -78,7 +78,7 @@
Copied
No info for this field
Take Photo
- Choose from gallery
+ From gallery
Check this!
Do not show again
the provided value is not allowed for this field