@@ -13,25 +13,32 @@ import android.app.Activity
1313import android.content.ActivityNotFoundException
1414import android.content.Context
1515import android.content.Intent
16- import android.graphics.Bitmap
1716import android.net.Uri
1817import android.provider.Settings
19- import android.util.Log
2018import android.widget.Toast
21- import androidx.compose.ui.graphics.ImageBitmap
22- import androidx.compose.ui.graphics.asAndroidBitmap
2319import androidx.core.content.FileProvider
20+ import androidx.core.net.toUri
2421import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
22+ import io.github.vinceglb.filekit.FileKit
23+ import io.github.vinceglb.filekit.ImageFormat
24+ import io.github.vinceglb.filekit.compressImage
2525import kotlinx.coroutines.Dispatchers
2626import kotlinx.coroutines.withContext
2727import org.jetbrains.compose.resources.ExperimentalResourceApi
28- import org.jetbrains.compose.resources.decodeToImageBitmap
2928import java.io.File
30- import java.io.FileOutputStream
31- import java.io.IOException
3229
30+ /* *
31+ * Actual implementation of [ShareUtils] for Android platform.
32+ *
33+ * This utility enables sharing of text and files (PDF, image, text) through Android's
34+ * native `Intent`-based sharing system.
35+ */
3336actual object ShareUtils {
3437
38+ /* *
39+ * Provider function to retrieve the current [Activity].
40+ * This must be set before using [shareText] or [shareFile].
41+ */
3542 private var activityProvider: () -> Activity = {
3643 throw IllegalArgumentException (
3744 " You need to implement the 'activityProvider' to provide the required Activity. " +
@@ -40,11 +47,23 @@ actual object ShareUtils {
4047 )
4148 }
4249
50+ /* *
51+ * Sets the activity provider function to be used internally for context retrieval.
52+ *
53+ * This is required to initialize before calling any sharing methods.
54+ *
55+ * @param provider A lambda that returns the current [Activity].
56+ */
4357 fun setActivityProvider (provider : () -> Activity ) {
4458 activityProvider = provider
4559 }
4660
47- actual fun shareText (text : String ) {
61+ /* *
62+ * Shares plain text content using an Android share sheet (`Intent.ACTION_SEND`).
63+ *
64+ * @param text The text content to share.
65+ */
66+ actual suspend fun shareText (text : String ) {
4867 val intent = Intent (Intent .ACTION_SEND ).apply {
4968 type = " text/plain"
5069 putExtra(Intent .EXTRA_TEXT , text)
@@ -53,65 +72,99 @@ actual object ShareUtils {
5372 activityProvider.invoke().startActivity(intentChooser)
5473 }
5574
56- actual suspend fun shareImage (title : String , image : ImageBitmap ) {
75+ /* *
76+ * Shares a file (e.g. PDF, text, image) using Android's file sharing mechanism.
77+ *
78+ * If the file is an image, it is compressed before sharing.
79+ * The file is temporarily saved to internal cache and shared using a `FileProvider`.
80+ *
81+ * @param file A [ShareFileModel] containing file metadata and binary content.
82+ */
83+ @OptIn(ExperimentalResourceApi ::class )
84+ actual suspend fun shareFile (file : ShareFileModel ) {
5785 val context = activityProvider.invoke().application.baseContext
5886
59- val uri = saveImage(image.asAndroidBitmap(), context)
60-
61- val sendIntent: Intent = Intent ().apply {
62- action = Intent .ACTION_SEND
63- putExtra(Intent .EXTRA_STREAM , uri)
64- setDataAndType(uri, " image/png" )
65- addFlags(Intent .FLAG_GRANT_READ_URI_PERMISSION )
87+ try {
88+ withContext(Dispatchers .IO ) {
89+ val compressedBytes = if (file.mime == MimeType .IMAGE ) {
90+ compressImage(file.bytes)
91+ } else {
92+ file.bytes
93+ }
94+
95+ val savedFile = saveFile(file.fileName, compressedBytes, context = context)
96+ val uri = FileProvider .getUriForFile(
97+ context,
98+ " ${context.packageName} .provider" ,
99+ savedFile,
100+ )
101+
102+ withContext(Dispatchers .Main ) {
103+ val intent = Intent (Intent .ACTION_SEND ).apply {
104+ putExtra(Intent .EXTRA_STREAM , uri)
105+ flags + = Intent .FLAG_ACTIVITY_NEW_TASK
106+ flags + = Intent .FLAG_GRANT_READ_URI_PERMISSION
107+ setDataAndType(uri, file.mime.toAndroidMimeType())
108+ }
109+ val chooser = Intent .createChooser(intent, null )
110+ activityProvider.invoke().startActivity(chooser)
111+ }
112+ }
113+ } catch (e: Exception ) {
114+ println (" Failed to share file: ${e.message} " )
66115 }
67-
68- val shareIntent = Intent .createChooser(sendIntent, title)
69- activityProvider.invoke().startActivity(shareIntent)
70116 }
71117
72- @OptIn( ExperimentalResourceApi :: class )
73- actual suspend fun shareImage ( title : String , byte : ByteArray ) {
74- Log .d( " Sharing QR Code " , " $title , size: ${byte.size} bytes " )
75- val context = activityProvider.invoke().application.baseContext
76- val imageBitmap = byte.decodeToImageBitmap()
77-
78- val uri = saveImage(imageBitmap.asAndroidBitmap(), context)
79-
80- val sendIntent : Intent = Intent (). apply {
81- action = Intent . ACTION_SEND
82- putExtra( Intent . EXTRA_STREAM , uri )
83- setDataAndType(uri, " image/png " )
84- addFlags( Intent . FLAG_GRANT_READ_URI_PERMISSION )
85- }
118+ /* *
119+ * Saves the provided byte array as a temporary file in the internal cache directory.
120+ *
121+ * @param name The name of the file to be saved.
122+ * @param data Byte array representing the file content.
123+ * @param context Android [Context] used to access the cache directory.
124+ * @return The saved [File] object.
125+ */
126+ private fun saveFile ( name : String , data : ByteArray , context : Context ): File {
127+ val cache = context.cacheDir
128+ val savedFile = File (cache, name )
129+ savedFile.writeBytes(data )
130+ return savedFile
131+ }
86132
87- val shareIntent = Intent .createChooser(sendIntent, title)
88- activityProvider.invoke().startActivity(shareIntent)
133+ /* *
134+ * Maps [MimeType] to a corresponding Android MIME type string.
135+ *
136+ * @return Android-compatible MIME type string.
137+ */
138+ private fun MimeType.toAndroidMimeType (): String = when (this ) {
139+ MimeType .PDF -> " application/pdf"
140+ MimeType .TEXT -> " text/plain"
141+ MimeType .IMAGE -> " image/*"
89142 }
90143
91- private suspend fun saveImage ( image : Bitmap , context : Context ): Uri ? {
92- return withContext( Dispatchers . IO ) {
93- try {
94- val imagesFolder = File (context.cacheDir, " images " )
95- imagesFolder.mkdirs()
96- val file = File (imagesFolder, " shared_image.png " )
97-
98- val stream = FileOutputStream (file)
99- image.compress( Bitmap . CompressFormat . PNG , 100 , stream)
100- stream.flush( )
101- stream.close()
102-
103- FileProvider .getUriForFile(context, " ${context.packageName} .provider " , file)
104- } catch (e : IOException ) {
105- Log .d( " saving bitmap " , " saving bitmap error ${e.message} " )
106- null
107- }
108- }
144+ /* *
145+ * Compresses an image file using [FileKit] logic.
146+ *
147+ * @param imageBytes The original image byte array.
148+ * @return A compressed image as a byte array.
149+ */
150+ private suspend fun compressImage ( imageBytes : ByteArray ): ByteArray {
151+ return FileKit .compressImage(
152+ bytes = imageBytes,
153+ // Compression quality (0–100 )
154+ quality = 100 ,
155+ // Max width in pixels
156+ maxWidth = 1024 ,
157+ // Max height in pixels
158+ maxHeight = 1024 ,
159+ // Image format (e.g., PNG or JPEG)
160+ imageFormat = ImageFormat . PNG ,
161+ )
109162 }
110163
111164 actual fun callHelpline () {
112165 val context = activityProvider.invoke().application.baseContext
113166 val intent = Intent (Intent .ACTION_DIAL ).apply {
114- data = Uri .parse( " tel:8000000000" )
167+ data = " tel:8000000000" .toUri( )
115168 addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
116169 }
117170
@@ -122,7 +175,7 @@ actual object ShareUtils {
122175 val context = activityProvider.invoke().application.baseContext
123176
124177 val intent = Intent (Intent .ACTION_SENDTO ).apply {
125- data = Uri .parse( " mailto:" )
178+ data = " mailto:" .toUri( )
126179 putExtra(
Intent .
EXTRA_EMAIL , arrayOf(
" [email protected] " ))
127180 putExtra(Intent .EXTRA_SUBJECT , " User Query" )
128181 addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
@@ -162,7 +215,7 @@ actual object ShareUtils {
162215
163216 actual fun openUrl (url : String ) {
164217 val context = activityProvider.invoke().application.baseContext
165- val uri = url.let { Uri .parse(url ) } ? : return
218+ val uri = url.let { url.toUri( ) }
166219 val intent = Intent (Intent .ACTION_VIEW ).apply {
167220 data = uri
168221 addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
0 commit comments