diff --git a/README.md b/README.md index 1f46fb20d..1f066c0be 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ that can be used as a dependency in any other wallet based project. It is develo | :feature:merchants | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | | :feature:notification | Done | ✅ | ✅ | ❔ | ✅ | ❔ | | :feature:qr | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | -| :feature:receipt | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:receipt | Done | ✅ | ✅ | ❔ | ✅ | ❔ | | :feature:request-money | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | | :feature:saved-cards | Done | ✅ | ✅ | ❔ | ✅ | ❔ | | :feature:search | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | diff --git a/feature/receipt/README.md b/feature/receipt/README.md new file mode 100644 index 000000000..32ce869a9 --- /dev/null +++ b/feature/receipt/README.md @@ -0,0 +1,178 @@ +# Download & View Receipt Feature + +This feature is responsible for download and view the transaction receipt. unfortunately, +this functionality of this feature is not working. due to configuration issue of `Savings Transaction Receipt` run report API. +Tested in Web, Postman and Swagger, but it's not working. + +And this feature could be merge with `Accounting` feature, to download the transaction receipt. + + +## Receiving Template of the Receipt +``` json +{ + "id": 157, + "reportName": "Savings Transaction Receipt", + "reportType": "Pentaho", + "coreReport": false, + "useReport": true, + "reportParameters": [ + { + "id": 426, + "parameterId": 1006, + "parameterName": "transactionId", + "reportParameterName": "transactionId" + } + ], + "allowedReportTypes": [ + "Table", + "Chart", + "SMS" + ], + "allowedReportSubTypes": [ + "Bar", + "Pie" + ], + "allowedParameters": [ + { + "id": 1, + "parameterName": "startDateSelect" + }, + { + "id": 2, + "parameterName": "endDateSelect" + }, + { + "id": 3, + "parameterName": "obligDateTypeSelect" + }, + { + "id": 5, + "parameterName": "OfficeIdSelectOne" + }, + { + "id": 6, + "parameterName": "loanOfficerIdSelectAll" + }, + { + "id": 10, + "parameterName": "currencyIdSelectAll" + }, + { + "id": 20, + "parameterName": "fundIdSelectAll" + }, + { + "id": 25, + "parameterName": "loanProductIdSelectAll" + }, + { + "id": 26, + "parameterName": "loanPurposeIdSelectAll" + }, + { + "id": 100, + "parameterName": "parTypeSelect" + }, + { + "id": 1004, + "parameterName": "selectAccount" + }, + { + "id": 1005, + "parameterName": "savingsProductIdSelectAll" + }, + { + "id": 1006, + "parameterName": "transactionId" + }, + { + "id": 1007, + "parameterName": "selectCenterId" + }, + { + "id": 1008, + "parameterName": "SelectGLAccountNO" + }, + { + "id": 1009, + "parameterName": "asOnDate" + }, + { + "id": 1010, + "parameterName": "SavingsAccountSubStatus" + }, + { + "id": 1011, + "parameterName": "cycleXSelect" + }, + { + "id": 1012, + "parameterName": "cycleYSelect" + }, + { + "id": 1013, + "parameterName": "fromXSelect" + }, + { + "id": 1014, + "parameterName": "toYSelect" + }, + { + "id": 1015, + "parameterName": "overdueXSelect" + }, + { + "id": 1016, + "parameterName": "overdueYSelect" + }, + { + "id": 1017, + "parameterName": "DefaultLoan" + }, + { + "id": 1018, + "parameterName": "DefaultClient" + }, + { + "id": 1019, + "parameterName": "DefaultGroup" + }, + { + "id": 1020, + "parameterName": "SelectLoanType" + }, + { + "id": 1021, + "parameterName": "DefaultSavings" + }, + { + "id": 1022, + "parameterName": "DefaultSavingsTransactionId" + } + ] +} +``` + +## Error Receiving From API + +``` json +{ + "developerMessage": "The server is currently unable to handle the request , please try after some time.", + "httpStatusCode": "503", + "defaultUserMessage": "The server is currently unable to handle the request , please try after some time.", + "userMessageGlobalisationCode": "error.msg.platform.service.unavailable", + "errors": [ + { + "developerMessage": "There is no ReportingProcessService registered in the ReportingProcessServiceProvider for this report type: Pentaho", + "defaultUserMessage": "There is no ReportingProcessService registered in the ReportingProcessServiceProvider for this report type: Pentaho", + "userMessageGlobalisationCode": "err.msg.report.service.implementation.missing", + "parameterName": "id", + "args": [ + { + "value": "Pentaho" + } + ] + } + ] +} +``` diff --git a/feature/receipt/build.gradle.kts b/feature/receipt/build.gradle.kts index 943600377..b5b19fd01 100644 --- a/feature/receipt/build.gradle.kts +++ b/feature/receipt/build.gradle.kts @@ -8,15 +8,23 @@ * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md */ plugins { - alias(libs.plugins.mifospay.android.feature) - alias(libs.plugins.mifospay.android.library.compose) + alias(libs.plugins.mifospay.cmp.feature) + alias(libs.plugins.kotlin.parcelize) } android { - namespace = "org.mifospay.receipt" + namespace = "org.mifospay.feature.receipt" } -dependencies { - // TODO:: this should be removed - implementation(libs.squareup.okhttp) +kotlin { + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.squareup.okio) + } + } } \ No newline at end of file diff --git a/feature/receipt/consumer-rules.pro b/feature/receipt/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/feature/receipt/proguard-rules.pro b/feature/receipt/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/feature/receipt/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/receipt/src/main/AndroidManifest.xml b/feature/receipt/src/androidMain/AndroidManifest.xml similarity index 100% rename from feature/receipt/src/main/AndroidManifest.xml rename to feature/receipt/src/androidMain/AndroidManifest.xml diff --git a/feature/receipt/src/main/res/drawable/feature_receipt_ic_download.xml b/feature/receipt/src/commonMain/composeResources/drawable/feature_receipt_ic_download.xml similarity index 100% rename from feature/receipt/src/main/res/drawable/feature_receipt_ic_download.xml rename to feature/receipt/src/commonMain/composeResources/drawable/feature_receipt_ic_download.xml diff --git a/feature/receipt/src/main/res/drawable/feature_receipt_mifospay_round_logo.png b/feature/receipt/src/commonMain/composeResources/drawable/feature_receipt_mifospay_round_logo.png similarity index 100% rename from feature/receipt/src/main/res/drawable/feature_receipt_mifospay_round_logo.png rename to feature/receipt/src/commonMain/composeResources/drawable/feature_receipt_mifospay_round_logo.png diff --git a/feature/receipt/src/main/res/values/strings.xml b/feature/receipt/src/commonMain/composeResources/values/strings.xml similarity index 100% rename from feature/receipt/src/main/res/values/strings.xml rename to feature/receipt/src/commonMain/composeResources/values/strings.xml diff --git a/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/ReceiptScreen.kt b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/ReceiptScreen.kt new file mode 100644 index 000000000..6ae2c4a2b --- /dev/null +++ b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/ReceiptScreen.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifospay.feature.receipt + +import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import mobile_wallet.feature.receipt.generated.resources.Res +import mobile_wallet.feature.receipt.generated.resources.feature_receipt_loading +import mobile_wallet.feature.receipt.generated.resources.feature_receipt_receipt +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.MifosLoadingWheel +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.ui.EmptyContentScreen + +@Composable +internal fun ReceiptScreenRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: ReceiptViewModel = koinViewModel(), +) { + val receiptUiState by viewModel.receiptUiState.collectAsState() + + ReceiptScreen( + uiState = receiptUiState, + onBackClick = onBackClick, + modifier = modifier, + ) +} + +@Composable +@VisibleForTesting +internal fun ReceiptScreen( + uiState: ReceiptUiState, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + MifosScaffold( + backPress = onBackClick, + topBarTitle = stringResource(Res.string.feature_receipt_receipt), + ) { + Box( + modifier = modifier + .fillMaxSize() + .padding(it), + ) { + when (uiState) { + is ReceiptUiState.Loading -> { + MifosLoadingWheel( + contentDesc = stringResource(Res.string.feature_receipt_loading), + ) + } + + is ReceiptUiState.Error -> { + EmptyContentScreen( + title = "Oops!", + subTitle = uiState.message, + ) + } + + is ReceiptUiState.Success -> { + EmptyContentScreen( + title = "Oops!", + subTitle = "Not implemented yet", + ) + } + } + } + } +} diff --git a/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/ReceiptViewModel.kt b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/ReceiptViewModel.kt new file mode 100644 index 000000000..c24d7178b --- /dev/null +++ b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/ReceiptViewModel.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifospay.feature.receipt + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.mifospay.core.model.savingsaccount.Transaction +import org.mifospay.core.model.savingsaccount.TransferDetail + +class ReceiptViewModel( + savedStateHandle: SavedStateHandle, +) : ViewModel() { + private val mReceiptState = + MutableStateFlow(ReceiptUiState.Error("Not implemented yet")) + val receiptUiState: StateFlow = mReceiptState.asStateFlow() +} + +sealed interface ReceiptUiState { + data class Success( + val transaction: Transaction, + val transferDetail: TransferDetail, + val receiptLink: String, + ) : ReceiptUiState + + data class Error( + val message: String, + ) : ReceiptUiState + + data object Loading : ReceiptUiState +} diff --git a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/di/ReceiptModule.kt b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/di/ReceiptModule.kt similarity index 56% rename from feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/di/ReceiptModule.kt rename to feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/di/ReceiptModule.kt index a59f7cd29..078dc6cd8 100644 --- a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/di/ReceiptModule.kt +++ b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/di/ReceiptModule.kt @@ -9,19 +9,10 @@ */ package org.mifospay.feature.receipt.di -import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module import org.mifospay.feature.receipt.ReceiptViewModel val ReceiptModule = module { - viewModel { - ReceiptViewModel( - mUseCaseHandler = get(), - preferencesHelper = get(), - downloadTransactionReceiptUseCase = get(), - fetchAccountTransactionUseCase = get(), - fetchAccountTransferUseCase = get(), - savedStateHandle = get(), - ) - } + viewModelOf(::ReceiptViewModel) } diff --git a/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/navigation/ReceiptNavigation.kt b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/navigation/ReceiptNavigation.kt new file mode 100644 index 000000000..bf9954eaa --- /dev/null +++ b/feature/receipt/src/commonMain/kotlin/org/mifospay/feature/receipt/navigation/ReceiptNavigation.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifospay.feature.receipt.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import org.mifospay.feature.receipt.ReceiptScreenRoute + +const val RECEIPT_ROUTE = "receipt_route" + +fun NavGraphBuilder.receiptScreen( + onBackClick: () -> Unit, +) { + composable( + route = RECEIPT_ROUTE, + ) { + ReceiptScreenRoute(onBackClick = onBackClick) + } +} + +fun NavController.navigateToReceipt() { + this.navigate(RECEIPT_ROUTE) +} diff --git a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/ReceiptScreen.kt b/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/ReceiptScreen.kt deleted file mode 100644 index faa322c2a..000000000 --- a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/ReceiptScreen.kt +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.receipt - -import android.Manifest -import android.content.ActivityNotFoundException -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent -import android.os.Build -import android.widget.Toast -import androidx.annotation.VisibleForTesting -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.content.FileProvider -import com.mifospay.core.model.domain.Transaction -import com.mifospay.core.model.domain.TransactionType -import com.mifospay.core.model.entity.accounts.savings.TransferDetail -import kotlinx.coroutines.launch -import org.koin.androidx.compose.koinViewModel -import org.mifospay.core.common.Constants -import org.mifospay.core.designsystem.component.FloatingActionButtonContent -import org.mifospay.core.designsystem.component.MifosOverlayLoadingWheel -import org.mifospay.core.designsystem.component.MifosScaffold -import org.mifospay.core.designsystem.component.PermissionBox -import org.mifospay.core.designsystem.icon.MifosIcons -import org.mifospay.core.designsystem.theme.MifosTheme -import org.mifospay.core.ui.DevicePreviews -import org.mifospay.receipt.R -import java.io.File - -@Composable -internal fun ReceiptScreenRoute( - openPassCodeActivity: () -> Unit, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - viewModel: ReceiptViewModel = koinViewModel(), -) { - /** - * This function serves as the androidMain entry point for the Receipt screen UI. - * It collects the receiptUiState and fileState from the ViewModel and - * calls the ReceiptScreen function, passing the collected states and - * other necessary parameters. - */ - val receiptUiState by viewModel.receiptUiState.collectAsState() - val fileState by viewModel.fileState.collectAsState() - - ReceiptScreen( - uiState = receiptUiState, - viewFileState = fileState, - downloadReceipt = viewModel::downloadReceipt, - openPassCodeActivity = openPassCodeActivity, - onBackClick = onBackClick, - modifier = modifier, - ) -} - -@Composable -@VisibleForTesting -internal fun ReceiptScreen( - uiState: ReceiptUiState, - viewFileState: PassFileState, - downloadReceipt: (String) -> Unit, - openPassCodeActivity: () -> Unit, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, -) { - /** - * This function renders the UI based on the ReceiptUiState and PassFileState. - * The UI is rendered based on the ReceiptUiState using a when expression. - */ - val context = LocalContext.current - - Box( - modifier = modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), - ) { - when (uiState) { - ReceiptUiState.Loading -> { - MifosOverlayLoadingWheel(contentDesc = stringResource(R.string.feature_receipt_loading)) - } - - ReceiptUiState.OpenPassCodeActivity -> { - openPassCodeActivity.invoke() - } - - is ReceiptUiState.Error -> { - val message = uiState.message - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } - - is ReceiptUiState.Success -> { - ReceiptScreenContent( - transaction = uiState.transaction, - transferDetail = uiState.transferDetail, - receiptLink = uiState.receiptLink, - downloadData = downloadReceipt, - file = viewFileState.file, - onBackClick = onBackClick, - ) - } - } - } -} - -/** - * The following function renders the actual content of the Receipt screen. - * It includes components like MifosScaffold, SnackbarHost, PermissionBox, - * and various UI elements like Text, Image, and Icon. - */ -@Composable -private fun ReceiptScreenContent( - transaction: Transaction, - transferDetail: TransferDetail, - receiptLink: String, - file: File, - downloadData: (String) -> Unit, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - val snackBarHostState = remember { SnackbarHostState() } - var needToHandlePermissions = rememberSaveable { false } - - val floatingActionButtonContent = FloatingActionButtonContent( - onClick = { - if (file.exists()) { - openReceiptFile(context, file) - } else { - needToHandlePermissions = true - } - }, - contentColor = MaterialTheme.colorScheme.onSurface, - content = { - Icon( - painter = painterResource(id = R.drawable.feature_receipt_ic_download), - contentDescription = stringResource(R.string.feature_receipt_downloading_receipt), - ) - }, - ) - - if (needToHandlePermissions) { - PermissionBox( - requiredPermissions = if (Build.VERSION.SDK_INT >= 33) { - listOf(Manifest.permission.READ_MEDIA_IMAGES) - } else { - listOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - ) - }, - title = R.string.feature_receipt_approve_permission_storage, - confirmButtonText = R.string.feature_receipt_proceed, - dismissButtonText = R.string.feature_receipt_dismiss, - description = R.string.feature_receipt_approve_permission_storage_receiptDescription, - onGranted = { - downloadData(transaction.transactionId.toString()) - LaunchedEffect(Unit) { - scope.launch { - val userAction = snackBarHostState.showSnackbar( - message = R.string.feature_receipt_download_complete.toString(), - actionLabel = R.string.feature_receipt_view_Receipt.toString(), - duration = SnackbarDuration.Indefinite, - withDismissAction = true, - ) - when (userAction) { - SnackbarResult.ActionPerformed -> { - openReceiptFile(context, file) - } - - SnackbarResult.Dismissed -> {} - } - } - } - }, - ) - } - - MifosScaffold( - topBarTitle = R.string.feature_receipt_receipt, - backPress = onBackClick, - floatingActionButtonContent = floatingActionButtonContent, - snackbarHost = { - SnackbarHost(hostState = snackBarHostState) - }, - scaffoldContent = { contentPadding -> - Box( - modifier = Modifier - .padding(contentPadding) - .fillMaxSize(), - ) { - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()), - ) { - Image( - painter = painterResource(id = R.drawable.feature_receipt_mifospay_round_logo), - contentDescription = stringResource(R.string.feature_receipt_pan_id), - modifier = Modifier - .size(120.dp) - .align(Alignment.CenterHorizontally), - ) - ReceiptHeaderBody(transaction, transferDetail) - Spacer(modifier = Modifier.size(height = 15.dp, width = 14.dp)) - ReceiptDetailsBody(transaction, transferDetail, receiptLink) - } - } - }, - modifier = modifier, - ) -} - -/** - * This function copies the given receiptLink to the system clipboard - * and displays a snackbar message to indicate successful copying. - * Used in ReceiptLinkActions. - */ -private fun copyToClipboard( - context: Context, - receiptLink: String, -) { - val clipboardManager = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText( - Constants.UNIQUE_RECEIPT_LINK, - receiptLink.trim { it <= ' ' }, - ) - clipboardManager.setPrimaryClip(clip) - onShowSnackbar(R.string.feature_receipt_unique_receipt_link_copied_to_clipboard, context) -} - -/** - * This function displays a toast message with the provided string resource. - * We will use onShowSnackbar(global Snackbar) when its added in navigation graph - */ -private fun onShowSnackbar(string: Int, context: Context) { - // onShowSnackbar(string,string) - Toast.makeText( - context, - string, - Toast.LENGTH_SHORT, - ).show() -} - -/** - * This function shares the given shareMessage using an Intent and is called in ReceiptLinkActions. - * It displays a chooser to select the app for sharing. - */ -private fun shareReceiptMessage( - shareMessage: String, - context: Context, -) { - val intent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, shareMessage) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - val chooserIntent = - Intent.createChooser(intent, context.getString(R.string.feature_receipt_share_receipt)) - - try { - context.startActivity(chooserIntent) - } catch (e: ActivityNotFoundException) { - onShowSnackbar(R.string.feature_receipt_sharing_link_failed, context) - } -} - -/** - * This function opens the given receipt file pdf using an Intent and is called in ReceiptScreen. - * It displays a chooser to select the app for opening the file. - */ -private fun openReceiptFile( - context: Context, - file: File, -) { - val data = FileProvider.getUriForFile( - context, - "org.mifospay.provider", - file, - ) - var intent: Intent? = Intent(Intent.ACTION_VIEW) - .setDataAndType(data, "application/pdf") - intent = Intent.createChooser(intent, context.getString(R.string.feature_receipt_view_receipt)) - - try { - context.startActivity(intent) - } catch (e: ActivityNotFoundException) { - onShowSnackbar(R.string.feature_receipt_opening_pdf_failed, context) - } -} - -@Composable -private fun ReceiptHeaderBody( - transaction: Transaction, - transferDetail: TransferDetail, - modifier: Modifier = Modifier, -) { - Column(modifier.fillMaxWidth()) { - val centerWithPaddingModifier = Modifier - .padding(horizontal = 8.dp) - .align(Alignment.CenterHorizontally) - - Text( - text = transaction.amount.toString(), - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.headlineLarge.fontSize, - ), - modifier = centerWithPaddingModifier.padding(top = 10.dp), - ) - - Text( - text = when (transaction.transactionType) { - TransactionType.DEBIT -> stringResource(R.string.feature_receipt_paid_to) - TransactionType.CREDIT -> stringResource(R.string.feature_receipt_credited_by) - TransactionType.OTHER -> stringResource(R.string.feature_receipt_other) - }, - color = when (transaction.transactionType) { - TransactionType.DEBIT -> Color.Red - TransactionType.CREDIT -> Color.Cyan - TransactionType.OTHER -> Color.Black - }, - style = MaterialTheme.typography.bodyLarge, - modifier = centerWithPaddingModifier.padding(top = 8.dp), - ) - - Text( - text = - if (transaction.transactionType == TransactionType.DEBIT) { - transferDetail.toClient.displayName - } else { - transferDetail.fromClient.displayName - }, - fontSize = 20.sp, - modifier = centerWithPaddingModifier.padding(top = 8.dp), - ) - } -} - -@Composable -private fun ReceiptDetailsBody( - transaction: Transaction, - transferDetail: TransferDetail, - receiptLink: String, - modifier: Modifier = Modifier, -) { - /** - * This function renders the transaction details and receipt link section, - * displaying information such as transaction ID, date, account details, and the - * receipt link. - */ - Column( - modifier = modifier - .padding(horizontal = 30.dp) - .fillMaxWidth(), - ) { - Text( - text = stringResource(R.string.feature_receipt_transaction_id), - style = TextStyle( - Color.Gray, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - Text( - text = transaction.transactionId.toString(), - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - - Spacer(modifier = Modifier.size(height = 30.dp, width = 14.dp)) - - Text( - text = stringResource(R.string.feature_receipt_transaction_date), - style = TextStyle( - Color.Gray, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - Text( - text = transaction.date.toString(), - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - - Spacer(modifier = Modifier.size(height = 30.dp, width = 14.dp)) - - Text( - text = stringResource(R.string.feature_receipt_to), - style = TextStyle( - Color.Gray, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - Text( - text = stringResource(R.string.feature_receipt_name) + transferDetail.toClient.displayName, - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - Text( - text = stringResource(R.string.feature_receipt_account_no) + transferDetail.toAccount.accountNo, - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - - Spacer(modifier = Modifier.size(height = 30.dp, width = 14.dp)) - - Text( - text = stringResource(R.string.feature_receipt_from), - style = TextStyle( - Color.Gray, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - Text( - text = stringResource(R.string.feature_receipt_name) + transferDetail.fromClient.displayName, - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - Text( - text = stringResource(R.string.feature_receipt_account_no) + transferDetail.fromAccount.accountNo, - style = TextStyle( - MaterialTheme.colorScheme.onSurface, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - - Spacer(modifier = Modifier.size(height = 30.dp, width = 14.dp)) - - Text( - text = stringResource(R.string.feature_receipt_unique_receipt_link), - style = TextStyle( - Color.Gray, - MaterialTheme.typography.bodyLarge.fontSize, - ), - ) - - ReceiptLinkActions(transferDetail, receiptLink) - } -} - -@Composable -private fun ReceiptLinkActions( - transferDetail: TransferDetail, - receiptLink: String, - modifier: Modifier = Modifier, -) { - /** - * This function renders the copy and share icons at the bottom of the screen, - * allowing users to copy the receipt link or share the receipt message. - */ - val context = LocalContext.current - val prepareShareMessage = - Constants.RECEIPT_SHARING_MESSAGE + transferDetail.fromClient.displayName + - Constants.TO + - transferDetail.toClient.displayName + - Constants.COLON + - receiptLink.trim { it <= ' ' } - - Row( - modifier = modifier - .fillMaxWidth(), - ) { - Text( - text = receiptLink, - style = TextStyle( - MaterialTheme.colorScheme.primary, - MaterialTheme.typography.bodyMedium.fontSize, - ), - ) - - Spacer(modifier = Modifier.size(height = 0.dp, width = 5.dp)) - - Icon( - MifosIcons.Copy, - contentDescription = stringResource(R.string.feature_receipt_copy_link), - modifier = Modifier - .size(25.dp) - .clickable { - copyToClipboard(context, receiptLink) - }, - tint = MaterialTheme.colorScheme.onSurface, - ) - - Icon( - MifosIcons.Share, - contentDescription = stringResource(R.string.feature_receipt_share_receipt), - modifier = Modifier - .padding(horizontal = 10.dp) - .size(25.dp) - .clickable { - shareReceiptMessage(prepareShareMessage, context) - }, - tint = MaterialTheme.colorScheme.onSurface, - ) - } -} - -@DevicePreviews -@Composable -private fun MultiScreenReceiptPreviewWithDummyData() { - ReceiptScreenContent( - transaction = Transaction( - "12345", - 12345, - 12345, - 312.0, - "01/04/2024", - com.mifospay.core.model.domain.Currency(), - TransactionType.DEBIT, - 12345, - TransferDetail(), - "12345", - ), - transferDetail = TransferDetail(), - receiptLink = "https://receipt.mifospay.com/12345", - downloadData = {}, - file = File("/path/to/receipt.pdf"), - onBackClick = {}, - ) -} - -@Preview(showBackground = true) -@Composable -private fun ReceiptPreviewWithLoading() { - MifosTheme { - ReceiptScreen( - uiState = ReceiptUiState.Loading, - viewFileState = PassFileState(file = File(" ")), - downloadReceipt = {}, - openPassCodeActivity = {}, - onBackClick = {}, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun ReceiptPreviewWithErrorMessage() { - MifosTheme { - ReceiptScreen( - uiState = ReceiptUiState.Error(stringResource(R.string.feature_receipt_error_specific_transactions)), - viewFileState = PassFileState(file = File(" ")), - downloadReceipt = {}, - openPassCodeActivity = {}, - onBackClick = {}, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun ReceiptPreviewWithSuccess() { - MifosTheme { - ReceiptScreen( - uiState = ReceiptUiState.Success( - Transaction(), - TransferDetail(), - receiptLink = "https://receipt.mifospay.com/12345", - ), - viewFileState = PassFileState(file = File(" ")), - downloadReceipt = {}, - openPassCodeActivity = {}, - onBackClick = {}, - ) - } -} diff --git a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/ReceiptViewModel.kt b/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/ReceiptViewModel.kt deleted file mode 100644 index 991bfc1c4..000000000 --- a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/ReceiptViewModel.kt +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.receipt - -import android.net.Uri -import android.os.Environment -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.mifospay.core.model.domain.Transaction -import com.mifospay.core.model.entity.accounts.savings.TransferDetail -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import okhttp3.ResponseBody -import org.mifospay.core.common.Constants -import org.mifospay.core.common.createPlatformFileUtils -import org.mifospay.core.data.base.UseCase -import org.mifospay.core.data.base.UseCaseHandler -import org.mifospay.core.data.domain.usecase.account.DownloadTransactionReceipt -import org.mifospay.core.data.domain.usecase.account.FetchAccountTransaction -import org.mifospay.core.data.domain.usecase.account.FetchAccountTransfer -import org.mifospay.core.datastore.PreferencesHelper -import java.io.File - -class ReceiptViewModel( - private val mUseCaseHandler: UseCaseHandler, - private val preferencesHelper: PreferencesHelper, - private val downloadTransactionReceiptUseCase: DownloadTransactionReceipt, - private val fetchAccountTransactionUseCase: FetchAccountTransaction, - private val fetchAccountTransferUseCase: FetchAccountTransfer, - savedStateHandle: SavedStateHandle, -) : ViewModel() { - private val mReceiptState = MutableStateFlow(ReceiptUiState.Loading) - val receiptUiState: StateFlow = mReceiptState.asStateFlow() - - private val mFileState = MutableStateFlow(PassFileState()) - val fileState: StateFlow = mFileState.asStateFlow() - - init { - savedStateHandle.get("uri")?.let { - getTransactionData(Uri.parse(it)) - } - } - - fun downloadReceipt(transactionId: String?) { - mUseCaseHandler.execute( - downloadTransactionReceiptUseCase, - DownloadTransactionReceipt.RequestValues(transactionId), - object : UseCase.UseCaseCallback { - override fun onSuccess(response: DownloadTransactionReceipt.ResponseValue) { - val filename = Constants.RECEIPT + transactionId + Constants.PDF - writeReceiptToPDF(response.responseBody, filename) - } - - override fun onError(message: String) { - mReceiptState.value = ReceiptUiState.Error(message) - } - }, - ) - } - - fun writeReceiptToPDF(responseBody: ResponseBody?, filename: String) { - viewModelScope.launch { - val mifosDirectory = File( - Environment.getExternalStorageDirectory(), - Constants.MIFOSPAY, - ) - if (!mifosDirectory.exists()) { - mifosDirectory.mkdirs() - } - val documentFile = File(mifosDirectory.path, filename) - val fileUtils = createPlatformFileUtils() - val result = fileUtils.writeInputStreamDataToFile( - responseBody!!.bytes(), - documentFile.path, - ) - if (result) { - mFileState.value = PassFileState(documentFile) - } - } - } - - private fun getTransactionData(data: Uri?) { - if (data != null) { - val params = data.pathSegments - val transactionId = params.getOrNull(0) - val receiptLink = data.toString() - fetchTransaction(transactionId, receiptLink) - } - } - - private fun fetchTransaction(transactionId: String?, receiptLink: String?) { - val accountId = preferencesHelper.accountId - - if (transactionId != null) { - mUseCaseHandler.execute( - fetchAccountTransactionUseCase, - FetchAccountTransaction.RequestValues(accountId, transactionId.toLong()), - object : UseCase.UseCaseCallback { - override fun onSuccess(response: FetchAccountTransaction.ResponseValue) { - if (receiptLink != null) { - fetchTransfer( - response.transaction, - response.transaction.transferId, - receiptLink, - ) - } - } - - override fun onError(message: String) { - if (message == Constants.UNAUTHORIZED_ERROR) { - mReceiptState.value = ReceiptUiState.OpenPassCodeActivity - } else { - mReceiptState.value = ReceiptUiState.Error(message) - } - } - }, - ) - } - } - - fun fetchTransfer( - transaction: Transaction, - transferId: Long, - receiptLink: String, - ) { - mUseCaseHandler.execute( - fetchAccountTransferUseCase, - FetchAccountTransfer.RequestValues(transferId), - object : UseCase.UseCaseCallback { - override fun onSuccess(response: FetchAccountTransfer.ResponseValue?) { - if (response != null) { - mReceiptState.value = - ReceiptUiState.Success( - transaction, - response.transferDetail, - receiptLink, - ) - } - } - - override fun onError(message: String) { - mReceiptState.value = ReceiptUiState.Error(message) - } - }, - ) - } -} - -data class PassFileState( - val file: File = File(""), -) - -sealed interface ReceiptUiState { - data class Success( - val transaction: Transaction, - val transferDetail: TransferDetail, - val receiptLink: String, - ) : ReceiptUiState - - data object OpenPassCodeActivity : ReceiptUiState - data class Error( - val message: String, - ) : ReceiptUiState - - data object Loading : ReceiptUiState -} diff --git a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/navigation/ReceiptNavigation.kt b/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/navigation/ReceiptNavigation.kt deleted file mode 100644 index cdebae0d9..000000000 --- a/feature/receipt/src/main/kotlin/org/mifospay/feature/receipt/navigation/ReceiptNavigation.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md - */ -package org.mifospay.feature.receipt.navigation - -import android.net.Uri -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType -import androidx.navigation.compose.composable -import androidx.navigation.navArgument -import org.mifospay.feature.receipt.ReceiptScreenRoute - -const val RECEIPT_ROUTE = "receipt_route" - -fun NavGraphBuilder.receiptScreen( - openPassCodeActivity: (Uri) -> Unit, - onBackClick: () -> Unit, -) { - composable( - route = "$RECEIPT_ROUTE?uri={uri}", - arguments = listOf( - navArgument("uri") { type = NavType.StringType }, - ), - ) { backStackEntry -> - val uriString = backStackEntry.arguments?.getString("uri") - val uri = if (uriString != null) Uri.parse(uriString) else null - - ReceiptScreenRoute( - openPassCodeActivity = { uri?.let { openPassCodeActivity(it) } }, - onBackClick = onBackClick, - ) - } -} - -fun NavController.navigateToReceipt(uri: Uri) { - this.navigate("$RECEIPT_ROUTE?uri=${Uri.encode(uri.toString())}") -} diff --git a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt index eeaf53268..46baf561c 100644 --- a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt +++ b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt @@ -2360,6 +2360,38 @@ | | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.0.20 (*) | | | \--- org.jetbrains.kotlin:kotlin-reflect:1.9.24 -> 2.0.20 (*) | | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) +| +--- project :feature:receipt +| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.8.6 (*) +| | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 (*) +| | +--- androidx.tracing:tracing-ktx:1.3.0-alpha02 (*) +| | +--- io.insert-koin:koin-bom:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-android:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-compose:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-androidx-navigation:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-core-viewmodel:4.0.0-RC2 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) +| | +--- io.insert-koin:koin-core:4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*) +| | +--- project :core:ui (*) +| | +--- project :core:designsystem (*) +| | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4 -> 4.0.0-RC2 (*) +| | +--- org.jetbrains.compose.runtime:runtime:1.7.0-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.2 -> 2.8.3-rc01 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.2 (*) +| | +--- org.jetbrains.androidx.savedstate:savedstate:1.2.2 (*) +| | +--- org.jetbrains.androidx.core:core-bundle:1.0.1 (*) +| | +--- org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8 (*) +| | +--- org.jetbrains.compose.ui:ui:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.foundation:foundation:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.material3:material3:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) +| | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) +| | +--- com.squareup.okio:okio:3.9.1 (*) +| | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (*) | +--- io.insert-koin:koin-annotations:1.4.0-RC4 (*) | +--- project :core:ui (*) diff --git a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt index 27c7a7287..3e5d591fa 100644 --- a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt +++ b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt @@ -20,6 +20,7 @@ :feature:notification :feature:payments :feature:profile +:feature:receipt :feature:savedcards :feature:settings :libs:country-code-picker diff --git a/mifospay-shared/build.gradle.kts b/mifospay-shared/build.gradle.kts index 68842d98e..c78d5d593 100644 --- a/mifospay-shared/build.gradle.kts +++ b/mifospay-shared/build.gradle.kts @@ -49,6 +49,7 @@ kotlin { api(projects.feature.kyc) api(projects.feature.notification) api(projects.feature.savedcards) + api(projects.feature.receipt) } desktopMain.dependencies { diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt index f817e5ae9..d824f8007 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt @@ -32,6 +32,7 @@ import org.mifospay.feature.kyc.di.KYCModule import org.mifospay.feature.notification.di.NotificationModule import org.mifospay.feature.payments.di.PaymentsModule import org.mifospay.feature.profile.di.ProfileModule +import org.mifospay.feature.receipt.di.ReceiptModule import org.mifospay.feature.savedcards.di.SavedCardsModule import org.mifospay.feature.settings.di.SettingsModule import org.mifospay.shared.MifosPayViewModel @@ -70,6 +71,7 @@ object KoinModules { KYCModule, NotificationModule, SavedCardsModule, + ReceiptModule, ) } private val LibraryModule = module { diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt index e2f34a25e..2dd2f0e1e 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt @@ -55,6 +55,7 @@ import org.mifospay.feature.payments.PaymentsScreenContents import org.mifospay.feature.payments.RequestScreen import org.mifospay.feature.payments.paymentsScreen import org.mifospay.feature.profile.navigation.profileNavGraph +import org.mifospay.feature.receipt.navigation.receiptScreen import org.mifospay.feature.savedcards.CardsScreen import org.mifospay.feature.savedcards.createOrUpdate.addEditCardScreen import org.mifospay.feature.savedcards.createOrUpdate.navigateToCardAddEdit @@ -229,5 +230,9 @@ internal fun MifosNavHost( addEditCardScreen( navigateBack = navController::navigateUp, ) + + receiptScreen( + onBackClick = navController::navigateUp, + ) } }