@@ -12,22 +12,28 @@ package org.mifospay.core.ui.utils
1212import android.app.Activity
1313import android.content.Context
1414import android.content.Intent
15- import android.graphics.Bitmap
16- import android.net.Uri
17- import android.util.Log
18- import androidx.compose.ui.graphics.ImageBitmap
19- import androidx.compose.ui.graphics.asAndroidBitmap
2015import androidx.core.content.FileProvider
16+ import co.touchlab.kermit.Logger
17+ import io.github.vinceglb.filekit.FileKit
18+ import io.github.vinceglb.filekit.ImageFormat
19+ import io.github.vinceglb.filekit.compressImage
2120import kotlinx.coroutines.Dispatchers
2221import kotlinx.coroutines.withContext
2322import org.jetbrains.compose.resources.ExperimentalResourceApi
24- import org.jetbrains.compose.resources.decodeToImageBitmap
2523import java.io.File
26- import java.io.FileOutputStream
27- import java.io.IOException
2824
25+ /* *
26+ * Actual implementation of [ShareUtils] for Android platform.
27+ *
28+ * This utility enables sharing of text and files (PDF, image, text) through Android's
29+ * native `Intent`-based sharing system.
30+ */
2931actual object ShareUtils {
3032
33+ /* *
34+ * Provider function to retrieve the current [Activity].
35+ * This must be set before using [shareText] or [shareFile].
36+ */
3137 private var activityProvider: () -> Activity = {
3238 throw IllegalArgumentException (
3339 " You need to implement the 'activityProvider' to provide the required Activity. " +
@@ -36,11 +42,23 @@ actual object ShareUtils {
3642 )
3743 }
3844
45+ /* *
46+ * Sets the activity provider function to be used internally for context retrieval.
47+ *
48+ * This is required to initialize before calling any sharing methods.
49+ *
50+ * @param provider A lambda that returns the current [Activity].
51+ */
3952 fun setActivityProvider (provider : () -> Activity ) {
4053 activityProvider = provider
4154 }
4255
43- actual fun shareText (text : String ) {
56+ /* *
57+ * Shares plain text content using an Android share sheet (`Intent.ACTION_SEND`).
58+ *
59+ * @param text The text content to share.
60+ */
61+ actual suspend fun shareText (text : String ) {
4462 val intent = Intent (Intent .ACTION_SEND ).apply {
4563 type = " text/plain"
4664 putExtra(Intent .EXTRA_TEXT , text)
@@ -49,57 +67,93 @@ actual object ShareUtils {
4967 activityProvider.invoke().startActivity(intentChooser)
5068 }
5169
52- actual suspend fun shareImage (title : String , image : ImageBitmap ) {
53- val context = activityProvider.invoke().application.baseContext
54-
55- val uri = saveImage(image.asAndroidBitmap(), context)
56-
57- val sendIntent: Intent = Intent ().apply {
58- action = Intent .ACTION_SEND
59- putExtra(Intent .EXTRA_STREAM , uri)
60- setDataAndType(uri, " image/png" )
61- addFlags(Intent .FLAG_GRANT_READ_URI_PERMISSION )
62- }
63-
64- val shareIntent = Intent .createChooser(sendIntent, title)
65- activityProvider.invoke().startActivity(shareIntent)
66- }
67-
70+ /* *
71+ * Shares a file (e.g. PDF, text, image) using Android's file sharing mechanism.
72+ *
73+ * If the file is an image, it is compressed before sharing.
74+ * The file is temporarily saved to internal cache and shared using a `FileProvider`.
75+ *
76+ * @param file A [ShareFileModel] containing file metadata and binary content.
77+ */
6878 @OptIn(ExperimentalResourceApi ::class )
69- actual suspend fun shareImage ( title : String , byte : ByteArray ) {
79+ actual suspend fun shareFile ( file : ShareFileModel ) {
7080 val context = activityProvider.invoke().application.baseContext
71- val imageBitmap = byte.decodeToImageBitmap()
7281
73- val uri = saveImage(imageBitmap.asAndroidBitmap(), context)
74-
75- val sendIntent: Intent = Intent ().apply {
76- action = Intent .ACTION_SEND
77- putExtra(Intent .EXTRA_STREAM , uri)
78- setDataAndType(uri, " image/png" )
79- addFlags(Intent .FLAG_GRANT_READ_URI_PERMISSION )
82+ try {
83+ withContext(Dispatchers .IO ) {
84+ val compressedBytes = if (file.mime == MimeType .IMAGE ) {
85+ compressImage(file.bytes)
86+ } else {
87+ file.bytes
88+ }
89+
90+ val savedFile = saveFile(file.fileName, compressedBytes, context = context)
91+ val uri = FileProvider .getUriForFile(
92+ context,
93+ " ${context.packageName} .provider" ,
94+ savedFile,
95+ )
96+
97+ withContext(Dispatchers .Main ) {
98+ val intent = Intent (Intent .ACTION_SEND ).apply {
99+ putExtra(Intent .EXTRA_STREAM , uri)
100+ flags + = Intent .FLAG_ACTIVITY_NEW_TASK
101+ flags + = Intent .FLAG_GRANT_READ_URI_PERMISSION
102+ setDataAndType(uri, file.mime.toAndroidMimeType())
103+ }
104+ val chooser = Intent .createChooser(intent, null )
105+ activityProvider.invoke().startActivity(chooser)
106+ }
107+ }
108+ } catch (e: Exception ) {
109+ e.printStackTrace()
110+ Logger .e(e) { " Failed to share file: ${e.message} " }
80111 }
81-
82- val shareIntent = Intent .createChooser(sendIntent, title)
83- activityProvider.invoke().startActivity(shareIntent)
84112 }
85113
86- private suspend fun saveImage (image : Bitmap , context : Context ): Uri ? {
87- return withContext(Dispatchers .IO ) {
88- try {
89- val imagesFolder = File (context.cacheDir, " images" )
90- imagesFolder.mkdirs()
91- val file = File (imagesFolder, " shared_image.png" )
114+ /* *
115+ * Saves the provided byte array as a temporary file in the internal cache directory.
116+ *
117+ * @param name The name of the file to be saved.
118+ * @param data Byte array representing the file content.
119+ * @param context Android [Context] used to access the cache directory.
120+ * @return The saved [File] object.
121+ */
122+ private fun saveFile (name : String , data : ByteArray , context : Context ): File {
123+ val cache = context.cacheDir
124+ val savedFile = File (cache, name)
125+ savedFile.writeBytes(data)
126+ return savedFile
127+ }
92128
93- val stream = FileOutputStream (file)
94- image.compress(Bitmap .CompressFormat .PNG , 100 , stream)
95- stream.flush()
96- stream.close()
129+ /* *
130+ * Maps [MimeType] to a corresponding Android MIME type string.
131+ *
132+ * @return Android-compatible MIME type string.
133+ */
134+ private fun MimeType.toAndroidMimeType (): String = when (this ) {
135+ MimeType .PDF -> " application/pdf"
136+ MimeType .TEXT -> " text/plain"
137+ MimeType .IMAGE -> " image/*"
138+ }
97139
98- FileProvider .getUriForFile(context, " ${context.packageName} .provider" , file)
99- } catch (e: IOException ) {
100- Log .d(" saving bitmap" , " saving bitmap error ${e.message} " )
101- null
102- }
103- }
140+ /* *
141+ * Compresses an image file using [FileKit] logic.
142+ *
143+ * @param imageBytes The original image byte array.
144+ * @return A compressed image as a byte array.
145+ */
146+ private suspend fun compressImage (imageBytes : ByteArray ): ByteArray {
147+ return FileKit .compressImage(
148+ bytes = imageBytes,
149+ // Compression quality (0–100)
150+ quality = 100 ,
151+ // Max width in pixels
152+ maxWidth = 1024 ,
153+ // Max height in pixels
154+ maxHeight = 1024 ,
155+ // Image format (e.g., PNG or JPEG)
156+ imageFormat = ImageFormat .PNG ,
157+ )
104158 }
105159}
0 commit comments