mirror of
https://github.com/openMF/mifos-mobile.git
synced 2026-02-06 11:26:51 +00:00
feat(core-ui): implement cross-platform file and text sharing (#2974)
This commit is contained in:
parent
0cc8a020c0
commit
f65ef09d8a
@ -17,6 +17,7 @@ internal fun Project.configureDetekt(extension: DetektExtension) = extension.app
|
||||
exclude("**/generated/**")
|
||||
exclude("**/build-logic/**")
|
||||
exclude("**/spotless/**")
|
||||
exclude("core-base/designsystem/**")
|
||||
reports {
|
||||
xml.required.set(true)
|
||||
html.required.set(true)
|
||||
|
||||
@ -43,8 +43,8 @@ kotlin{
|
||||
implementation(compose.components.resources)
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
implementation(libs.jb.composeNavigation)
|
||||
implementation(libs.filekit.compose)
|
||||
implementation(libs.filekit.core)
|
||||
implementation(libs.filekit.dialog.compose)
|
||||
implementation(libs.compottie.resources)
|
||||
implementation(libs.compottie.lite)
|
||||
}
|
||||
|
||||
@ -13,25 +13,32 @@ import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||
import io.github.vinceglb.filekit.FileKit
|
||||
import io.github.vinceglb.filekit.ImageFormat
|
||||
import io.github.vinceglb.filekit.compressImage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
import org.jetbrains.compose.resources.decodeToImageBitmap
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Actual implementation of [ShareUtils] for Android platform.
|
||||
*
|
||||
* This utility enables sharing of text and files (PDF, image, text) through Android's
|
||||
* native `Intent`-based sharing system.
|
||||
*/
|
||||
actual object ShareUtils {
|
||||
|
||||
/**
|
||||
* Provider function to retrieve the current [Activity].
|
||||
* This must be set before using [shareText] or [shareFile].
|
||||
*/
|
||||
private var activityProvider: () -> Activity = {
|
||||
throw IllegalArgumentException(
|
||||
"You need to implement the 'activityProvider' to provide the required Activity. " +
|
||||
@ -40,11 +47,23 @@ actual object ShareUtils {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity provider function to be used internally for context retrieval.
|
||||
*
|
||||
* This is required to initialize before calling any sharing methods.
|
||||
*
|
||||
* @param provider A lambda that returns the current [Activity].
|
||||
*/
|
||||
fun setActivityProvider(provider: () -> Activity) {
|
||||
activityProvider = provider
|
||||
}
|
||||
|
||||
actual fun shareText(text: String) {
|
||||
/**
|
||||
* Shares plain text content using an Android share sheet (`Intent.ACTION_SEND`).
|
||||
*
|
||||
* @param text The text content to share.
|
||||
*/
|
||||
actual suspend fun shareText(text: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
@ -53,65 +72,99 @@ actual object ShareUtils {
|
||||
activityProvider.invoke().startActivity(intentChooser)
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, image: ImageBitmap) {
|
||||
val context = activityProvider.invoke().application.baseContext
|
||||
|
||||
val uri = saveImage(image.asAndroidBitmap(), context)
|
||||
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
setDataAndType(uri, "image/png")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, title)
|
||||
activityProvider.invoke().startActivity(shareIntent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares a file (e.g. PDF, text, image) using Android's file sharing mechanism.
|
||||
*
|
||||
* If the file is an image, it is compressed before sharing.
|
||||
* The file is temporarily saved to internal cache and shared using a `FileProvider`.
|
||||
*
|
||||
* @param file A [ShareFileModel] containing file metadata and binary content.
|
||||
*/
|
||||
@OptIn(ExperimentalResourceApi::class)
|
||||
actual suspend fun shareImage(title: String, byte: ByteArray) {
|
||||
Log.d("Sharing QR Code", " $title, size: ${byte.size} bytes")
|
||||
actual suspend fun shareFile(file: ShareFileModel) {
|
||||
val context = activityProvider.invoke().application.baseContext
|
||||
val imageBitmap = byte.decodeToImageBitmap()
|
||||
|
||||
val uri = saveImage(imageBitmap.asAndroidBitmap(), context)
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
val compressedBytes = if (file.mime == MimeType.IMAGE) {
|
||||
compressImage(file.bytes)
|
||||
} else {
|
||||
file.bytes
|
||||
}
|
||||
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
setDataAndType(uri, "image/png")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
val savedFile = saveFile(file.fileName, compressedBytes, context = context)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.provider",
|
||||
savedFile,
|
||||
)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
flags += Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
flags += Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
setDataAndType(uri, file.mime.toAndroidMimeType())
|
||||
}
|
||||
val chooser = Intent.createChooser(intent, null)
|
||||
activityProvider.invoke().startActivity(chooser)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Failed to share file: ${e.message}")
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, title)
|
||||
activityProvider.invoke().startActivity(shareIntent)
|
||||
}
|
||||
|
||||
private suspend fun saveImage(image: Bitmap, context: Context): Uri? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val imagesFolder = File(context.cacheDir, "images")
|
||||
imagesFolder.mkdirs()
|
||||
val file = File(imagesFolder, "shared_image.png")
|
||||
/**
|
||||
* Saves the provided byte array as a temporary file in the internal cache directory.
|
||||
*
|
||||
* @param name The name of the file to be saved.
|
||||
* @param data Byte array representing the file content.
|
||||
* @param context Android [Context] used to access the cache directory.
|
||||
* @return The saved [File] object.
|
||||
*/
|
||||
private fun saveFile(name: String, data: ByteArray, context: Context): File {
|
||||
val cache = context.cacheDir
|
||||
val savedFile = File(cache, name)
|
||||
savedFile.writeBytes(data)
|
||||
return savedFile
|
||||
}
|
||||
|
||||
val stream = FileOutputStream(file)
|
||||
image.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
||||
stream.flush()
|
||||
stream.close()
|
||||
/**
|
||||
* Maps [MimeType] to a corresponding Android MIME type string.
|
||||
*
|
||||
* @return Android-compatible MIME type string.
|
||||
*/
|
||||
private fun MimeType.toAndroidMimeType(): String = when (this) {
|
||||
MimeType.PDF -> "application/pdf"
|
||||
MimeType.TEXT -> "text/plain"
|
||||
MimeType.IMAGE -> "image/*"
|
||||
}
|
||||
|
||||
FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
|
||||
} catch (e: IOException) {
|
||||
Log.d("saving bitmap", "saving bitmap error ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compresses an image file using [FileKit] logic.
|
||||
*
|
||||
* @param imageBytes The original image byte array.
|
||||
* @return A compressed image as a byte array.
|
||||
*/
|
||||
private suspend fun compressImage(imageBytes: ByteArray): ByteArray {
|
||||
return FileKit.compressImage(
|
||||
bytes = imageBytes,
|
||||
// Compression quality (0–100)
|
||||
quality = 100,
|
||||
// Max width in pixels
|
||||
maxWidth = 1024,
|
||||
// Max height in pixels
|
||||
maxHeight = 1024,
|
||||
// Image format (e.g., PNG or JPEG)
|
||||
imageFormat = ImageFormat.PNG,
|
||||
)
|
||||
}
|
||||
|
||||
actual fun callHelpline() {
|
||||
val context = activityProvider.invoke().application.baseContext
|
||||
val intent = Intent(Intent.ACTION_DIAL).apply {
|
||||
data = Uri.parse("tel:8000000000")
|
||||
data = "tel:8000000000".toUri()
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
|
||||
@ -122,7 +175,7 @@ actual object ShareUtils {
|
||||
val context = activityProvider.invoke().application.baseContext
|
||||
|
||||
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
||||
data = Uri.parse("mailto:")
|
||||
data = "mailto:".toUri()
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf("support@mifos.org"))
|
||||
putExtra(Intent.EXTRA_SUBJECT, "User Query")
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
@ -162,7 +215,7 @@ actual object ShareUtils {
|
||||
|
||||
actual fun openUrl(url: String) {
|
||||
val context = activityProvider.invoke().application.baseContext
|
||||
val uri = url.let { Uri.parse(url) } ?: return
|
||||
val uri = url.let { url.toUri() }
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
data = uri
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
@ -170,9 +170,10 @@ fun MifosDashboardCard(
|
||||
@Composable
|
||||
fun MifosAccountApplyDashboard(
|
||||
onOpenAccountClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
MifosCustomCard(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.padding(horizontal = DesignToken.padding.largeIncreased)
|
||||
.border(
|
||||
0.5.dp,
|
||||
|
||||
@ -9,15 +9,30 @@
|
||||
*/
|
||||
package org.mifos.mobile.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
|
||||
/**
|
||||
* Platform-specific utilities for sharing content such as text and files.
|
||||
*
|
||||
* This expect declaration should be implemented for each platform (e.g., Android, iOS) to handle
|
||||
* the specifics of sharing functionality.
|
||||
*/
|
||||
expect object ShareUtils {
|
||||
|
||||
fun shareText(text: String)
|
||||
/**
|
||||
* Shares plain text content using the platform's native sharing mechanism.
|
||||
*
|
||||
* @param text The text content to be shared.
|
||||
*/
|
||||
suspend fun shareText(text: String)
|
||||
|
||||
suspend fun shareImage(title: String, image: ImageBitmap)
|
||||
|
||||
suspend fun shareImage(title: String, byte: ByteArray)
|
||||
/**
|
||||
* Shares a file using the platform's native sharing mechanism.
|
||||
*
|
||||
* This is a suspend function, allowing for asynchronous operations such as file preparation
|
||||
* or permission handling if needed.
|
||||
*
|
||||
* @param file A [ShareFileModel] containing the file's metadata and content.
|
||||
*/
|
||||
suspend fun shareFile(file: ShareFileModel)
|
||||
|
||||
fun openAppInfo()
|
||||
|
||||
@ -31,3 +46,45 @@ expect object ShareUtils {
|
||||
|
||||
fun ossLicensesMenuActivity()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents supported MIME types for file sharing.
|
||||
*/
|
||||
enum class MimeType {
|
||||
PDF,
|
||||
TEXT,
|
||||
IMAGE,
|
||||
}
|
||||
|
||||
/**
|
||||
* Model representing a file to be shared.
|
||||
*
|
||||
* @property mime The MIME type of the file. Defaults to [MimeType.PDF].
|
||||
* @property fileName The name of the file, including its extension.
|
||||
* @property bytes The binary content of the file.
|
||||
*/
|
||||
data class ShareFileModel(
|
||||
val mime: MimeType = MimeType.PDF,
|
||||
val fileName: String,
|
||||
val bytes: ByteArray,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as ShareFileModel
|
||||
|
||||
if (mime != other.mime) return false
|
||||
if (fileName != other.fileName) return false
|
||||
if (!bytes.contentEquals(other.bytes)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = mime.hashCode()
|
||||
result = 31 * result + fileName.hashCode()
|
||||
result = 31 * result + bytes.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,30 +9,48 @@
|
||||
*/
|
||||
package org.mifos.mobile.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asSkiaBitmap
|
||||
import io.github.vinceglb.filekit.core.FileKit
|
||||
import io.github.vinceglb.filekit.FileKit
|
||||
import io.github.vinceglb.filekit.dialogs.openFileSaver
|
||||
import io.github.vinceglb.filekit.write
|
||||
import java.awt.Desktop
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* JVM-specific implementation of [ShareUtils] for desktop platforms.
|
||||
*
|
||||
* This object simulates file sharing by prompting the user with a "Save As" dialog
|
||||
* using [FileKit.openFileSaver]. It allows the user to save the content locally,
|
||||
* which is the most suitable alternative to "sharing" in desktop environments.
|
||||
*/
|
||||
actual object ShareUtils {
|
||||
actual fun shareText(text: String) {
|
||||
/**
|
||||
* Prompts the user to save the given text content as a file on their system.
|
||||
*
|
||||
* This method uses [FileKit.openFileSaver] to open a native "Save As" dialog,
|
||||
* and writes the provided text to the selected file location.
|
||||
*
|
||||
* @param text The plain text content the user will save to disk.
|
||||
*/
|
||||
actual suspend fun shareText(text: String) {
|
||||
val newFile = FileKit.openFileSaver(
|
||||
suggestedName = "text.txt",
|
||||
)
|
||||
newFile?.write(text.encodeToByteArray())
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, image: ImageBitmap) {
|
||||
FileKit.saveFile(
|
||||
bytes = image.asSkiaBitmap().readPixels(),
|
||||
baseName = "MifosQrCode",
|
||||
extension = "png",
|
||||
)
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, byte: ByteArray) {
|
||||
FileKit.saveFile(
|
||||
bytes = byte,
|
||||
baseName = "MifosQrCode",
|
||||
extension = "png",
|
||||
/**
|
||||
* Prompts the user to save a binary file (e.g., image, PDF) to their local system.
|
||||
*
|
||||
* This is used as a desktop-friendly alternative to file sharing, using
|
||||
* [FileKit.openFileSaver] to let the user choose the destination file path.
|
||||
*
|
||||
* @param file The file to be "shared", including its filename and byte content.
|
||||
*/
|
||||
actual suspend fun shareFile(file: ShareFileModel) {
|
||||
val newFile = FileKit.openFileSaver(
|
||||
suggestedName = file.fileName,
|
||||
)
|
||||
newFile?.write(file.bytes)
|
||||
}
|
||||
|
||||
actual fun callHelpline() {
|
||||
|
||||
@ -9,31 +9,42 @@
|
||||
*/
|
||||
package org.mifos.mobile.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asSkiaBitmap
|
||||
import io.github.vinceglb.filekit.core.FileKit
|
||||
import io.github.vinceglb.filekit.FileKit
|
||||
import io.github.vinceglb.filekit.download
|
||||
import kotlinx.browser.window
|
||||
|
||||
/**
|
||||
* Provides utility functions for sharing content on JS and WASM platforms.
|
||||
*
|
||||
* This implementation uses [FileKit.download] to trigger file downloads
|
||||
* in web environments (JS), as native share dialogs are not supported
|
||||
* on these platforms.
|
||||
*/
|
||||
actual object ShareUtils {
|
||||
actual fun shareText(text: String) {
|
||||
/**
|
||||
* Shares plain text content by triggering a file download.
|
||||
*
|
||||
* The text is saved to a file named `text.txt` and offered to the user
|
||||
* as a downloadable file in the browser.
|
||||
*
|
||||
* @param text The plain text content to be shared.
|
||||
*/
|
||||
actual suspend fun shareText(text: String) {
|
||||
FileKit.download(bytes = text.encodeToByteArray(), fileName = "text.txt")
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, image: ImageBitmap) {
|
||||
FileKit.saveFile(
|
||||
bytes = image.asSkiaBitmap().readPixels(),
|
||||
baseName = "MifosQrCode",
|
||||
extension = "png",
|
||||
)
|
||||
/**
|
||||
* Shares a file by triggering a download of the file's byte content.
|
||||
*
|
||||
* This method creates a download link in the browser for the given
|
||||
* [ShareFileModel.bytes], using the provided [ShareFileModel.fileName]
|
||||
* as the download file name.
|
||||
*
|
||||
* @param file The [ShareFileModel] containing file name and content to be downloaded.
|
||||
*/
|
||||
actual suspend fun shareFile(file: ShareFileModel) {
|
||||
FileKit.download(bytes = file.bytes, fileName = file.fileName)
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, byte: ByteArray) {
|
||||
FileKit.saveFile(
|
||||
bytes = byte,
|
||||
baseName = "MifosQrCode",
|
||||
extension = "png",
|
||||
)
|
||||
}
|
||||
|
||||
actual fun callHelpline() {
|
||||
window.alert("Calling is not supported on Web. Please contact support at 8000000000.")
|
||||
}
|
||||
|
||||
@ -9,18 +9,109 @@
|
||||
*/
|
||||
package org.mifos.mobile.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import io.github.vinceglb.filekit.FileKit
|
||||
import io.github.vinceglb.filekit.ImageFormat
|
||||
import io.github.vinceglb.filekit.PlatformFile
|
||||
import io.github.vinceglb.filekit.absolutePath
|
||||
import io.github.vinceglb.filekit.cacheDir
|
||||
import io.github.vinceglb.filekit.compressImage
|
||||
import io.github.vinceglb.filekit.dialogs.shareFile
|
||||
import io.github.vinceglb.filekit.write
|
||||
import platform.Foundation.NSURL
|
||||
import platform.UIKit.UIActivityViewController
|
||||
import platform.UIKit.UIApplication
|
||||
import platform.UIKit.UIApplicationOpenSettingsURLString
|
||||
import platform.UIKit.UIViewController
|
||||
|
||||
/**
|
||||
* Actual implementation of [ShareUtils] for iOS platform.
|
||||
*
|
||||
* Provides functionality to share text and files using iOS native `UIActivityViewController`.
|
||||
*/
|
||||
actual object ShareUtils {
|
||||
actual fun shareText(text: String) {
|
||||
/**
|
||||
* Shares plain text using the iOS share sheet (`UIActivityViewController`).
|
||||
*
|
||||
* @param text The text content to be shared.
|
||||
*/
|
||||
actual suspend fun shareText(text: String) {
|
||||
val currentViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
|
||||
val activityViewController = UIActivityViewController(listOf(text), null)
|
||||
currentViewController?.presentViewController(
|
||||
viewControllerToPresent = activityViewController,
|
||||
animated = true,
|
||||
completion = null,
|
||||
)
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, image: ImageBitmap) {
|
||||
/**
|
||||
* Shares a file (image or other binary) using the iOS share sheet.
|
||||
*
|
||||
* If the file is an image, it will be compressed before sharing.
|
||||
*
|
||||
* @param file The file metadata and byte content to share.
|
||||
*/
|
||||
actual suspend fun shareFile(file: ShareFileModel) {
|
||||
try {
|
||||
val compressedBytes = if (file.mime == MimeType.IMAGE) {
|
||||
compressImage(file.bytes)
|
||||
} else {
|
||||
file.bytes
|
||||
}
|
||||
|
||||
val fileToShare = saveFile(data = compressedBytes, fileName = file.fileName)
|
||||
FileKit.shareFile(fileToShare)
|
||||
} catch (e: Exception) {
|
||||
println("Failed to share file: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, byte: ByteArray) {
|
||||
/**
|
||||
* Saves a byte array as a file inside the iOS app's cache directory.
|
||||
*
|
||||
* Converts the resulting file path to a properly scoped [NSURL],
|
||||
* which is necessary for iOS to allow sharing via `UIActivityViewController`.
|
||||
*
|
||||
* @param data The file content to write.
|
||||
* @param fileName The name of the file to create.
|
||||
* @return A [PlatformFile] backed by a scoped `NSURL`, ready for sharing.
|
||||
*/
|
||||
private suspend fun saveFile(data: ByteArray, fileName: String): PlatformFile {
|
||||
val tempFile = PlatformFile(FileKit.cacheDir, fileName)
|
||||
tempFile.write(data)
|
||||
|
||||
/**
|
||||
* iOS requires file URLs used in `UIActivityViewController` to be created
|
||||
* with `NSURL.fileURLWithPath(...)` to ensure they have proper sandbox access.
|
||||
*
|
||||
* If the file is created from a raw path string, the system may reject it
|
||||
* with a sandbox extension error (e.g., "Cannot issue sandbox extension for URL").
|
||||
*
|
||||
* Wrapping the path in `NSURL` ensures the file is treated as a valid
|
||||
* security-scoped resource.
|
||||
*/
|
||||
val nsUrl = NSURL.fileURLWithPath(tempFile.absolutePath())
|
||||
return PlatformFile(nsUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses an image file using [FileKit] logic.
|
||||
*
|
||||
* @param imageBytes The original image byte array.
|
||||
* @return A compressed image as a byte array.
|
||||
*/
|
||||
private suspend fun compressImage(imageBytes: ByteArray): ByteArray {
|
||||
return FileKit.compressImage(
|
||||
bytes = imageBytes,
|
||||
// Compression quality (0–100)
|
||||
quality = 100,
|
||||
// Max width in pixels
|
||||
maxWidth = 1024,
|
||||
// Max height in pixels
|
||||
maxHeight = 1024,
|
||||
// Image format (e.g., PNG or JPEG)
|
||||
imageFormat = ImageFormat.PNG,
|
||||
)
|
||||
}
|
||||
|
||||
actual fun callHelpline() {
|
||||
@ -40,9 +131,17 @@ actual object ShareUtils {
|
||||
}
|
||||
|
||||
actual fun openAppInfo() {
|
||||
val url = NSURL.URLWithString(UIApplicationOpenSettingsURLString)
|
||||
if (url != null && UIApplication.sharedApplication.canOpenURL(url)) {
|
||||
UIApplication.sharedApplication.openURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun shareApp() {
|
||||
// TODO Replace with app store link
|
||||
val appStoreUrl = "https://apps.apple.com/app/idXXXXXXXX"
|
||||
val activityVC = UIActivityViewController(activityItems = listOf(appStoreUrl), applicationActivities = null)
|
||||
present(activityVC)
|
||||
}
|
||||
|
||||
actual fun openUrl(url: String) {
|
||||
@ -54,4 +153,9 @@ actual object ShareUtils {
|
||||
|
||||
actual fun ossLicensesMenuActivity() {
|
||||
}
|
||||
|
||||
private fun present(controller: UIViewController) {
|
||||
val rootController = UIApplication.sharedApplication.keyWindow?.rootViewController
|
||||
rootController?.presentViewController(controller, animated = true, completion = null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,31 +9,42 @@
|
||||
*/
|
||||
package org.mifos.mobile.core.ui.utils
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asSkiaBitmap
|
||||
import io.github.vinceglb.filekit.core.FileKit
|
||||
import io.github.vinceglb.filekit.FileKit
|
||||
import io.github.vinceglb.filekit.download
|
||||
import kotlinx.browser.window
|
||||
|
||||
/**
|
||||
* Provides utility functions for sharing content on JS and WASM platforms.
|
||||
*
|
||||
* This implementation uses [FileKit.download] to trigger file downloads
|
||||
* in web environments (WASM), as native share dialogs are not supported
|
||||
* on these platforms.
|
||||
*/
|
||||
actual object ShareUtils {
|
||||
actual fun shareText(text: String) {
|
||||
/**
|
||||
* Shares plain text content by triggering a file download.
|
||||
*
|
||||
* The text is saved to a file named `text.txt` and offered to the user
|
||||
* as a downloadable file in the browser.
|
||||
*
|
||||
* @param text The plain text content to be shared.
|
||||
*/
|
||||
actual suspend fun shareText(text: String) {
|
||||
FileKit.download(bytes = text.encodeToByteArray(), fileName = "text.txt")
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, image: ImageBitmap) {
|
||||
FileKit.saveFile(
|
||||
bytes = image.asSkiaBitmap().readPixels(),
|
||||
baseName = "MifosQrCode",
|
||||
extension = "png",
|
||||
)
|
||||
/**
|
||||
* Shares a file by triggering a download of the file's byte content.
|
||||
*
|
||||
* This method creates a download link in the browser for the given
|
||||
* [ShareFileModel.bytes], using the provided [ShareFileModel.fileName]
|
||||
* as the download file name.
|
||||
*
|
||||
* @param file The [ShareFileModel] containing file name and content to be downloaded.
|
||||
*/
|
||||
actual suspend fun shareFile(file: ShareFileModel) {
|
||||
FileKit.download(bytes = file.bytes, fileName = file.fileName)
|
||||
}
|
||||
|
||||
actual suspend fun shareImage(title: String, byte: ByteArray) {
|
||||
FileKit.saveFile(
|
||||
bytes = byte,
|
||||
baseName = "MifosQrCode",
|
||||
extension = "png",
|
||||
)
|
||||
}
|
||||
|
||||
actual fun callHelpline() {
|
||||
window.alert("Calling is not supported on Web. Please contact support at 8000000000.")
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ internal fun BottomSheetContent(
|
||||
internal fun BottomSheetIconContainer(
|
||||
onClick: (BottomSheetItemType) -> Unit,
|
||||
) {
|
||||
BottomSheetItemType.entries.forEach { it ->
|
||||
BottomSheetItemType.entries.forEach {
|
||||
MifosCustomCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user