diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/Detekt.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/Detekt.kt index 44aa5ba22b..fc81973189 100644 --- a/build-logic/convention/src/main/kotlin/org/mifos/mobile/Detekt.kt +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/Detekt.kt @@ -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) diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index bc8c1d2f3d..e0e0c84b5a 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -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) } diff --git a/core/ui/src/androidMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.android.kt b/core/ui/src/androidMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.android.kt index a4083a97f7..fafa9dbf79 100644 --- a/core/ui/src/androidMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.android.kt +++ b/core/ui/src/androidMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.android.kt @@ -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) { + /** + * 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 shareFile(file: ShareFileModel) { 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) + try { + withContext(Dispatchers.IO) { + val compressedBytes = if (file.mime == MimeType.IMAGE) { + compressImage(file.bytes) + } else { + file.bytes + } + + 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) } - @OptIn(ExperimentalResourceApi::class) - actual suspend fun shareImage(title: String, byte: ByteArray) { - Log.d("Sharing QR Code", " $title, size: ${byte.size} bytes") - val context = activityProvider.invoke().application.baseContext - val imageBitmap = byte.decodeToImageBitmap() - - val uri = saveImage(imageBitmap.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) - } + /** + * 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 shareIntent = Intent.createChooser(sendIntent, title) - activityProvider.invoke().startActivity(shareIntent) + /** + * 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/*" } - 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") - - val stream = FileOutputStream(file) - image.compress(Bitmap.CompressFormat.PNG, 100, stream) - stream.flush() - stream.close() - - 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) diff --git a/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosDashboardCard.kt b/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosDashboardCard.kt index 3cdf76cb13..2b6d36c398 100644 --- a/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosDashboardCard.kt +++ b/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosDashboardCard.kt @@ -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, diff --git a/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.kt b/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.kt index 00ef3f834b..d06e015d86 100644 --- a/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.kt +++ b/core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.kt @@ -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) - - suspend fun shareImage(title: String, image: ImageBitmap) + /** + * 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, 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 + } +} diff --git a/core/ui/src/desktopMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.desktop.kt b/core/ui/src/desktopMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.desktop.kt index 4bd2486e0d..53334b4dd6 100644 --- a/core/ui/src/desktopMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.desktop.kt +++ b/core/ui/src/desktopMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.desktop.kt @@ -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) { - } - - actual suspend fun shareImage(title: String, image: ImageBitmap) { - FileKit.saveFile( - bytes = image.asSkiaBitmap().readPixels(), - baseName = "MifosQrCode", - extension = "png", + /** + * 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, 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() { diff --git a/core/ui/src/jsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.js.kt b/core/ui/src/jsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.js.kt index b0ef9df377..d3c1d030b3 100644 --- a/core/ui/src/jsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.js.kt +++ b/core/ui/src/jsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.js.kt @@ -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.") } diff --git a/core/ui/src/nativeMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.native.kt b/core/ui/src/nativeMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.native.kt index 648ac272ab..7541230b2f 100644 --- a/core/ui/src/nativeMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.native.kt +++ b/core/ui/src/nativeMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.native.kt @@ -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) + } } diff --git a/core/ui/src/wasmJsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.wasmJs.kt b/core/ui/src/wasmJsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.wasmJs.kt index 553b9d33c2..43ab235bcb 100644 --- a/core/ui/src/wasmJsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.wasmJs.kt +++ b/core/ui/src/wasmJsMain/kotlin/org/mifos/mobile/core/ui/utils/ShareUtils.wasmJs.kt @@ -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.") } diff --git a/feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/components/BottomSheetContent.kt b/feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/components/BottomSheetContent.kt index 46e0622105..3aea98341a 100644 --- a/feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/components/BottomSheetContent.kt +++ b/feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/components/BottomSheetContent.kt @@ -89,7 +89,7 @@ internal fun BottomSheetContent( internal fun BottomSheetIconContainer( onClick: (BottomSheetItemType) -> Unit, ) { - BottomSheetItemType.entries.forEach { it -> + BottomSheetItemType.entries.forEach { MifosCustomCard( modifier = Modifier .fillMaxWidth()