@@ -13,25 +13,32 @@ import android.app.Activity
13
13
import android.content.ActivityNotFoundException
14
14
import android.content.Context
15
15
import android.content.Intent
16
- import android.graphics.Bitmap
17
16
import android.net.Uri
18
17
import android.provider.Settings
19
- import android.util.Log
20
18
import android.widget.Toast
21
- import androidx.compose.ui.graphics.ImageBitmap
22
- import androidx.compose.ui.graphics.asAndroidBitmap
23
19
import androidx.core.content.FileProvider
20
+ import androidx.core.net.toUri
24
21
import 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
25
25
import kotlinx.coroutines.Dispatchers
26
26
import kotlinx.coroutines.withContext
27
27
import org.jetbrains.compose.resources.ExperimentalResourceApi
28
- import org.jetbrains.compose.resources.decodeToImageBitmap
29
28
import java.io.File
30
- import java.io.FileOutputStream
31
- import java.io.IOException
32
29
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
+ */
33
36
actual object ShareUtils {
34
37
38
+ /* *
39
+ * Provider function to retrieve the current [Activity].
40
+ * This must be set before using [shareText] or [shareFile].
41
+ */
35
42
private var activityProvider: () -> Activity = {
36
43
throw IllegalArgumentException (
37
44
" You need to implement the 'activityProvider' to provide the required Activity. " +
@@ -40,11 +47,23 @@ actual object ShareUtils {
40
47
)
41
48
}
42
49
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
+ */
43
57
fun setActivityProvider (provider : () -> Activity ) {
44
58
activityProvider = provider
45
59
}
46
60
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 ) {
48
67
val intent = Intent (Intent .ACTION_SEND ).apply {
49
68
type = " text/plain"
50
69
putExtra(Intent .EXTRA_TEXT , text)
@@ -53,65 +72,99 @@ actual object ShareUtils {
53
72
activityProvider.invoke().startActivity(intentChooser)
54
73
}
55
74
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 ) {
57
85
val context = activityProvider.invoke().application.baseContext
58
86
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} " )
66
115
}
67
-
68
- val shareIntent = Intent .createChooser(sendIntent, title)
69
- activityProvider.invoke().startActivity(shareIntent)
70
116
}
71
117
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
+ }
86
132
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/*"
89
142
}
90
143
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
+ )
109
162
}
110
163
111
164
actual fun callHelpline () {
112
165
val context = activityProvider.invoke().application.baseContext
113
166
val intent = Intent (Intent .ACTION_DIAL ).apply {
114
- data = Uri .parse( " tel:8000000000" )
167
+ data = " tel:8000000000" .toUri( )
115
168
addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
116
169
}
117
170
@@ -122,7 +175,7 @@ actual object ShareUtils {
122
175
val context = activityProvider.invoke().application.baseContext
123
176
124
177
val intent = Intent (Intent .ACTION_SENDTO ).apply {
125
- data = Uri .parse( " mailto:" )
178
+ data = " mailto:" .toUri( )
126
179
putExtra(
Intent .
EXTRA_EMAIL , arrayOf(
" [email protected] " ))
127
180
putExtra(Intent .EXTRA_SUBJECT , " User Query" )
128
181
addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
@@ -162,7 +215,7 @@ actual object ShareUtils {
162
215
163
216
actual fun openUrl (url : String ) {
164
217
val context = activityProvider.invoke().application.baseContext
165
- val uri = url.let { Uri .parse(url ) } ? : return
218
+ val uri = url.let { url.toUri( ) }
166
219
val intent = Intent (Intent .ACTION_VIEW ).apply {
167
220
data = uri
168
221
addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
0 commit comments