diff --git a/cmp-android/src/main/AndroidManifest.xml b/cmp-android/src/main/AndroidManifest.xml
index 235fab0a86c..a5a0331869c 100644
--- a/cmp-android/src/main/AndroidManifest.xml
+++ b/cmp-android/src/main/AndroidManifest.xml
@@ -70,14 +70,19 @@
+ android:grantUriPermissions="true"
+ tools:replace="android:authorities">
+
+ android:resource="@xml/file_paths"
+ tools:replace="android:resource" />
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/client/src/androidMain/res/xml/file_paths.xml b/feature/client/src/androidMain/res/xml/file_paths.xml
new file mode 100644
index 00000000000..3197349cc81
--- /dev/null
+++ b/feature/client/src/androidMain/res/xml/file_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsScreen.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsScreen.kt
index 210132b67f5..85d3175ac8a 100644
--- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsScreen.kt
+++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsScreen.kt
@@ -309,6 +309,7 @@ internal fun ClientDetailsScreen(
uploadImage = {
galleryLauncher.launch()
},
+
deleteImage = {
clientDetailsViewModel.deleteClientImage(clientId)
showSelectImageDialog = false
@@ -406,6 +407,7 @@ private fun MifosClientDetailsScreen(
value = it,
)
}
+
client?.mobileNo?.let {
MifosClientDetailsText(
icon = MifosIcons.MobileFriendly,
@@ -813,6 +815,7 @@ private fun MifosSelectImageDialog(
textAlign = TextAlign.Center,
)
}
+
Button(
onClick = { deleteImage() },
colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.secondary),
diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsViewModel.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsViewModel.kt
index a3029ecc90c..fd1c60e8e23 100644
--- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsViewModel.kt
+++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetails/ClientDetailsViewModel.kt
@@ -59,6 +59,10 @@ class ClientDetailsViewModel(
private val _showLoading = MutableStateFlow(true)
val showLoading = _showLoading.asStateFlow()
+// private val _lastUploadedFilePath = MutableStateFlow(null)
+// val lastUploadedFilePath = _lastUploadedFilePath.asStateFlow()
+
+
init {
viewModelScope.launch {
getUserProfile()
@@ -88,6 +92,30 @@ class ClientDetailsViewModel(
}
}
+ private fun uploadImages(id: Int, imageFile: PlatformFile) = viewModelScope.launch {
+ uploadClientImageUseCase(id, createImageRequestBody(imageFile)).collect { result ->
+ when (result) {
+ is DataState.Error -> {
+ _clientDetailsUiState.value =
+ ClientDetailsUiState.ShowError(result.message)
+ _showLoading.value = false
+ }
+
+ is DataState.Loading -> {
+ _showLoading.value = true
+ }
+
+ is DataState.Success -> {
+ _clientDetailsUiState.value = ClientDetailsUiState.ShowUploadImageSuccessfully(
+ result.data,
+ )
+ getUserProfile()
+ _showLoading.value = false
+ }
+ }
+ }
+ }
+
fun deleteClientImage(clientId: Int) = viewModelScope.launch {
_showLoading.value = true
try {
@@ -130,8 +158,10 @@ class ClientDetailsViewModel(
viewModelScope.launch {
try {
_showLoading.value = true
- val compressed = compressImage(imageFile, clientId.toString())
+
uploadImage(clientId, compressed)
+ uploadImages(clientId, compressed)
+
} catch (e: Exception) {
_showLoading.value = false
_clientDetailsUiState.value = ClientDetailsUiState.ShowError(e.message ?: "Unexpected error")
@@ -150,4 +180,6 @@ class ClientDetailsViewModel(
}
}
}
+
+
}
diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientIdentifiersDialog/ClientIdentifiersDialogScreen.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientIdentifiersDialog/ClientIdentifiersDialogScreen.kt
index 15899c8eebf..5ffd90af802 100644
--- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientIdentifiersDialog/ClientIdentifiersDialogScreen.kt
+++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientIdentifiersDialog/ClientIdentifiersDialogScreen.kt
@@ -263,7 +263,9 @@ private class ClientIdentifiersDialogUiStatePreview :
get() = sequenceOf(
ClientIdentifierDialogUiState.Loading,
ClientIdentifierDialogUiState.Error(Res.string.feature_client_failed_to_load_client_identifiers),
+
ClientIdentifierDialogUiState.IdentifierCreatedSuccessfully,
+
)
}
diff --git a/feature/document/build.gradle.kts b/feature/document/build.gradle.kts
index b75274e3602..f18af540ae5 100644
--- a/feature/document/build.gradle.kts
+++ b/feature/document/build.gradle.kts
@@ -31,10 +31,16 @@ kotlin {
implementation(libs.filekit.compose)
implementation(libs.filekit.dialog.compose)
implementation(compose.components.uiToolingPreview)
+
}
}
}
dependencies {
debugImplementation(compose.uiTooling)
-}
\ No newline at end of file
+}
+
+//dependencies {
+// implementation(libs.material)
+//
+//}
\ No newline at end of file
diff --git a/feature/document/src/androidMain/AndroidManifest.xml b/feature/document/src/androidMain/AndroidManifest.xml
index 1dc76da0f7e..6bd6d8edbc1 100644
--- a/feature/document/src/androidMain/AndroidManifest.xml
+++ b/feature/document/src/androidMain/AndroidManifest.xml
@@ -1,13 +1,32 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- See https://github.com/openMF/android-client/blob/master/LICENSE.md
--->
-
-
\ No newline at end of file
+
diff --git a/feature/document/src/androidMain/kotlin/com/mifos/feature/document/utils/MyApp.kt b/feature/document/src/androidMain/kotlin/com/mifos/feature/document/utils/MyApp.kt
new file mode 100644
index 00000000000..450fed43f72
--- /dev/null
+++ b/feature/document/src/androidMain/kotlin/com/mifos/feature/document/utils/MyApp.kt
@@ -0,0 +1,14 @@
+package com.mifos.feature.document.utils
+
+import android.app.Application
+
+// Global application context (Android only)
+lateinit var appContext: Application
+
+
+class MyApp : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ appContext = this
+ }
+}
diff --git a/feature/document/src/androidMain/kotlin/com/mifos/feature/document/utils/OpenPdf.android.kt b/feature/document/src/androidMain/kotlin/com/mifos/feature/document/utils/OpenPdf.android.kt
new file mode 100644
index 00000000000..0b22ac916fd
--- /dev/null
+++ b/feature/document/src/androidMain/kotlin/com/mifos/feature/document/utils/OpenPdf.android.kt
@@ -0,0 +1,39 @@
+package com.mifos.feature.document.utils
+
+import android.content.Intent
+import android.net.Uri
+import androidx.core.content.FileProvider
+import java.io.File
+
+actual fun openPdf(path: String) {
+ val file = File(path)
+ val uri: Uri = FileProvider.getUriForFile(
+ appContext,
+ "${appContext.packageName}.fileprovider",
+ file
+ )
+
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ setDataAndType(uri, "application/pdf")
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ appContext.startActivity(Intent.createChooser(intent, "Open PDF"))
+}
+
+actual fun openImage(filePath: String) {
+ // ✅ Use appContext instead of getApplicationContext()
+ val file = File(filePath)
+ val uri = FileProvider.getUriForFile(
+ appContext,
+ "${appContext.packageName}.fileprovider",
+ file
+ )
+
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ setDataAndType(uri, "image/*")
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ appContext.startActivity(intent)
+}
diff --git a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogScreen.kt b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogScreen.kt
index a5302a6e9d0..2717196245c 100644
--- a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogScreen.kt
+++ b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogScreen.kt
@@ -66,8 +66,11 @@ import com.mifos.core.designsystem.component.MifosCircularProgress
import com.mifos.core.designsystem.component.MifosOutlinedTextField
import com.mifos.core.designsystem.icon.MifosIcons
import com.mifos.core.model.objects.noncoreobjects.Document
+import com.mifos.feature.document.utils.openImage
+import com.mifos.feature.document.utils.openPdf
import io.github.vinceglb.filekit.PlatformFile
import io.github.vinceglb.filekit.name
+import io.github.vinceglb.filekit.path
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
@@ -97,6 +100,7 @@ internal fun DocumentDialogScreen(
var fileName by rememberSaveable { mutableStateOf(document?.name) }
var fileChosen by rememberSaveable { mutableStateOf(null) }
+
DocumentDialogScreen(
uiState = state,
modifier = modifier.background(MaterialTheme.colorScheme.background),
@@ -138,6 +142,22 @@ internal fun DocumentDialogScreen(
},
filename = fileName,
closeScreen = closeScreen,
+
+ onPreviewFile = { // 🔹 Added callback
+ fileChosen?.let { file ->
+ val path = file.path ?: return@let
+ if (file.name.endsWith(".pdf", ignoreCase = true)) {
+ openPdf(path) // 🔹 Use your expect/actual implementation
+ } else if (
+ file.name.endsWith(".jpg", true) ||
+ file.name.endsWith(".jpeg", true) ||
+ file.name.endsWith(".png", true)
+ ) {
+ openImage(path) // 🔹 New expect/actual function for image
+ }
+ }
+ }
+
)
}
@@ -148,12 +168,15 @@ internal fun DocumentDialogScreen(
snackbarHostState: SnackbarHostState,
document: Document?,
openFilePicker: () -> Unit,
- closeDialog: () -> Unit?,
+ closeDialog: () -> Unit,
uploadDocument: (String, String) -> Unit,
filename: String?,
modifier: Modifier = Modifier,
closeScreen: () -> Unit,
+ onPreviewFile: () -> Unit
+
) {
+
when (uiState) {
is DocumentDialogUiState.Initial -> {
DocumentDialogContent(
@@ -163,7 +186,8 @@ internal fun DocumentDialogScreen(
openFilePicker = openFilePicker,
uploadDocument = uploadDocument,
fileName = filename,
- modifier = modifier,
+ onPreviewFile = onPreviewFile,
+ modifier = modifier
)
}
@@ -211,7 +235,9 @@ private fun DocumentDialogContent(
openFilePicker: () -> Unit,
uploadDocument: (String, String) -> Unit,
fileName: String?,
+ onPreviewFile: () -> Unit,
modifier: Modifier = Modifier,
+
) {
var dialogTitle = stringResource(Res.string.feature_document_upload_document)
var name by rememberSaveable(stateSaver = TextFieldValue.Saver) {
@@ -357,6 +383,17 @@ private fun DocumentDialogContent(
onClick = openFilePicker,
)
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ if (!fileName.isNullOrEmpty()) {
+ DialogButton(
+ text = "Preview File",
+ onClick = onPreviewFile
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ }
+
Spacer(modifier = Modifier.height(20.dp))
DialogButton(
@@ -388,6 +425,8 @@ private fun DialogButton(
}
}
+
+
private class DocumentDialogPreviewProvider : PreviewParameterProvider {
override val values: Sequence
get() = sequenceOf(
@@ -415,5 +454,7 @@ private fun DocumentDialogPreview(
uploadDocument = { _, _ -> },
filename = "",
closeScreen = { },
+ onPreviewFile = { }
+
)
}
diff --git a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogViewModel.kt b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogViewModel.kt
index 0c3857e5aa8..67dcd2f59a3 100644
--- a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogViewModel.kt
+++ b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentDialog/DocumentDialogViewModel.kt
@@ -65,6 +65,7 @@ class DocumentDialogViewModel(
}
}
+
fun resetDialogUiState() {
_documentDialogUiState.value = DocumentDialogUiState.Initial
}
diff --git a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentList/DocumentListScreen.kt b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentList/DocumentListScreen.kt
index c9231639a0c..737e85f3ee3 100644
--- a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentList/DocumentListScreen.kt
+++ b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/documentList/DocumentListScreen.kt
@@ -144,6 +144,9 @@ internal fun DocumentListScreen(
onRemovedDocument = { documentId ->
viewModel.removeDocument(entityType, entityId, documentId)
},
+ onPreviewDocument = { documentId ->
+
+ }
)
}
@@ -159,6 +162,7 @@ internal fun DocumentListScreen(
onAddDocument: () -> Unit,
onDownloadDocument: (Int) -> Unit,
onUpdateDocument: (Document) -> Unit,
+ onPreviewDocument: (Document) -> Unit,
modifier: Modifier = Modifier,
onRemovedDocument: (Int) -> Unit,
) {
@@ -182,6 +186,11 @@ internal fun DocumentListScreen(
selectedDocument?.let { onUpdateDocument(it) }
showSelectOptionsDialog = !showSelectOptionsDialog
},
+ previewDocument={
+ selectedDocument?.let{onPreviewDocument(it)}
+
+ },
+
removeDocument = {
selectedDocument?.id?.let { onRemovedDocument(it) }
showSelectOptionsDialog = !showSelectOptionsDialog
@@ -323,12 +332,15 @@ private fun DocumentItem(
}
}
+
+
@Composable
private fun SelectOptionsDialog(
onDismissRequest: () -> Unit,
downloadDocument: () -> Unit,
updateDocument: () -> Unit,
removeDocument: () -> Unit,
+ previewDocument: () -> Unit,
) {
Dialog(
onDismissRequest = { onDismissRequest() },
@@ -382,6 +394,18 @@ private fun SelectOptionsDialog(
textAlign = TextAlign.Center,
)
}
+
+ MifosButton(
+ onClick = { previewDocument() },
+ ) {
+ Text(
+ text = "Preview Document",
+ modifier = Modifier.fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ )
+ }
+
}
}
}
@@ -400,6 +424,7 @@ private class DocumentListUiStateProvider : PreviewParameterProvider Unit) =
+ viewModelScope.launch {
+ downloadDocumentUseCase(entityType, entityId, documentId).collect { result ->
+ when (result) {
+ is DataState.Success -> {
+ val localPath: HttpResponse = result.data
+ // onPreviewReady(localPath)
+ }
+ else -> { }
+ }
+ }
+ }
+
}
diff --git a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/navigation/DocumentNavigation.kt b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/navigation/DocumentNavigation.kt
index 1b9c0a2342e..becedfd0552 100644
--- a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/navigation/DocumentNavigation.kt
+++ b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/navigation/DocumentNavigation.kt
@@ -9,6 +9,7 @@
*/
package com.mifos.feature.document.navigation
+import androidx.compose.ui.text.input.KeyboardType.Companion.Uri
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
@@ -39,3 +40,11 @@ fun NavGraphBuilder.documentListScreen(
fun NavController.navigateToDocumentListScreen(entityId: Int, entityType: String) {
navigate(DocumentScreens.DocumentListScreen.argument(entityId, entityType))
}
+
+
+
+
+
+
+
+
diff --git a/feature/document/src/commonMain/kotlin/com/mifos/feature/document/utils/OpenPdf.kt b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/utils/OpenPdf.kt
new file mode 100644
index 00000000000..25de1dacc6b
--- /dev/null
+++ b/feature/document/src/commonMain/kotlin/com/mifos/feature/document/utils/OpenPdf.kt
@@ -0,0 +1,6 @@
+package com.mifos.feature.document.utils
+
+
+expect fun openPdf(path: String)
+
+expect fun openImage(filePath: String)
\ No newline at end of file
diff --git a/feature/document/src/desktopMain/kotlin/com/mifos/feature/document/utils/OpenPdf.desktop.kt b/feature/document/src/desktopMain/kotlin/com/mifos/feature/document/utils/OpenPdf.desktop.kt
new file mode 100644
index 00000000000..0ecc1696684
--- /dev/null
+++ b/feature/document/src/desktopMain/kotlin/com/mifos/feature/document/utils/OpenPdf.desktop.kt
@@ -0,0 +1,15 @@
+package com.mifos.feature.document.utils
+
+
+import java.awt.Desktop
+import java.io.File
+
+actual fun openPdf(path: String) {
+ val file = File(path)
+ if (Desktop.isDesktopSupported()) {
+ Desktop.getDesktop().open(file)
+ }
+}
+
+actual fun openImage(filePath: String) {
+}
\ No newline at end of file
diff --git a/feature/document/src/iosMain/kotlin/com/mifos/feature/document/utils/OpenPdf.ios.kt b/feature/document/src/iosMain/kotlin/com/mifos/feature/document/utils/OpenPdf.ios.kt
new file mode 100644
index 00000000000..753a7a6d50a
--- /dev/null
+++ b/feature/document/src/iosMain/kotlin/com/mifos/feature/document/utils/OpenPdf.ios.kt
@@ -0,0 +1,14 @@
+package com.mifos.feature.document.utils
+
+import platform.Foundation.NSURL
+import platform.UIKit.UIApplication
+
+actual fun openPdf(path: String) {
+ val url = NSURL.fileURLWithPath(path)
+ if (UIApplication.sharedApplication.canOpenURL(url)) {
+ UIApplication.sharedApplication.openURL(url)
+ }
+}
+
+actual fun openImage(filePath: String) {
+}
\ No newline at end of file
diff --git a/feature/document/src/main/res/values/strings.xml b/feature/document/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..3330e714926
--- /dev/null
+++ b/feature/document/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Mifos Client
+
\ No newline at end of file
diff --git a/feature/document/src/main/res/values/themes.xml b/feature/document/src/main/res/values/themes.xml
new file mode 100644
index 00000000000..f11f7450a82
--- /dev/null
+++ b/feature/document/src/main/res/values/themes.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/feature/document/src/main/res/xml/file_paths.xml b/feature/document/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000000..3197349cc81
--- /dev/null
+++ b/feature/document/src/main/res/xml/file_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1733b452bd1..38437b0765c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -64,3 +64,5 @@ include(":feature:search")
include(":feature:settings")
//include(":feature:passcode")
include(":feature:search")
+
+