From 28f9bd5446d22e85569f9e720b4534e075fd3d69 Mon Sep 17 00:00:00 2001 From: Nagarjuna <99315689+Nagarjuna0033@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:47:59 +0530 Subject: [PATCH] Fix invoice api (#1797) * Redesign requeset screen UI * fix MissingKoinDefinitionException * removed comments and fixed share qr code bug * fix: Invoice APIs --- .../domain/usecase/invoice/FetchInvoice.kt | 7 +- .../domain/usecase/invoice/FetchInvoices.kt | 5 +- .../fineract/repository/FineractRepository.kt | 25 +- .../core/designsystem/component/IconBox.kt | 18 +- .../designsystem/component/MifosScaffold.kt | 4 + .../designsystem/component/MifosTopBar.kt | 4 + .../core/designsystem/icon/MifosIcons.kt | 4 + .../com/mifospay/core/model/entity/Invoice.kt | 49 --- .../core/model/entity/invoice/Invoice.kt | 35 +++ .../model/entity/invoice/InvoiceEntity.kt | 30 ++ .../core/network/MifosWalletOkHttpClient.kt | 13 +- .../core/network/TestingApiInterceptor.kt | 41 +++ .../mifospay/core/network/di/NetworkModule.kt | 14 + .../org/mifospay/core/network/di/Qualifier.kt | 2 +- .../core/network/services/InvoiceService.kt | 36 +-- .../kotlin/org/mifospay/core/ui/AvatarBox.kt | 48 +++ .../org/mifospay/core/ui/MifosDivider.kt | 33 ++ .../feature/invoices/InvoiceDetailScreen.kt | 295 ++++++++---------- .../invoices/InvoiceDetailViewModel.kt | 12 +- .../mifospay/feature/invoices/InvoiceItem.kt | 173 ++++------ .../feature/invoices/InvoiceScreen.kt | 89 ++++-- .../feature/invoices/InvoicesViewModel.kt | 2 +- .../feature/invoices/di/InvoicesModule.kt | 6 +- .../invoices/navigation/InvoiceNavigation.kt | 2 - .../invoices/src/main/res/values/strings.xml | 10 +- .../feature/request/money/GenerateQr.kt | 290 ++++++++++++++--- .../feature/request/money/ShowQrContent.kt | 138 ++++---- .../request/money/ShowQrScreenRoute.kt | 33 +- .../request/money/di/RequestMoneyModule.kt | 3 +- .../feature/request/money/util/ImageUtils.kt | 3 +- .../src/main/res/drawable/logo.png | Bin 0 -> 2459 bytes .../src/main/res/values/strings.xml | 2 + .../org/mifospay/navigation/MifosNavHost.kt | 6 +- .../java/org/mifospay/KoinModulesCheck.kt | 1 + 34 files changed, 879 insertions(+), 554 deletions(-) delete mode 100644 core/model/src/main/java/com/mifospay/core/model/entity/Invoice.kt create mode 100644 core/model/src/main/java/com/mifospay/core/model/entity/invoice/Invoice.kt create mode 100644 core/model/src/main/java/com/mifospay/core/model/entity/invoice/InvoiceEntity.kt create mode 100644 core/network/src/main/kotlin/org/mifospay/core/network/TestingApiInterceptor.kt create mode 100644 core/ui/src/main/kotlin/org/mifospay/core/ui/AvatarBox.kt create mode 100644 core/ui/src/main/kotlin/org/mifospay/core/ui/MifosDivider.kt create mode 100644 feature/request-money/src/main/res/drawable/logo.png diff --git a/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoice.kt b/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoice.kt index 4a6cf57ea..162f9a97d 100644 --- a/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoice.kt +++ b/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoice.kt @@ -11,7 +11,7 @@ package org.mifospay.core.data.domain.usecase.invoice import android.net.Uri import android.util.Log -import com.mifospay.core.model.entity.Invoice +import com.mifospay.core.model.entity.invoice.Invoice import org.mifospay.core.data.base.UseCase import org.mifospay.core.data.fineract.repository.FineractRepository import org.mifospay.core.data.util.Constants @@ -27,13 +27,12 @@ class FetchInvoice( class ResponseValue( val invoices: List, ) : UseCase.ResponseValue - override fun executeUseCase(requestValues: RequestValues) { val paymentLink = requestValues.uniquePaymentLink try { val params = paymentLink?.pathSegments - val clientId = params?.get(0) // "clientId" - val invoiceId = params?.get(1) // "invoiceId" + val clientId = params?.get(0)?.toLong() // "clientId" + val invoiceId = params?.get(1)?.toLong() // "invoiceId if (clientId != null && invoiceId != null) { mFineractRepository.fetchInvoice(clientId, invoiceId) .observeOn(AndroidSchedulers.mainThread()) diff --git a/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoices.kt b/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoices.kt index e9a4b5b2a..262a05383 100644 --- a/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoices.kt +++ b/core/data/src/main/java/org/mifospay/core/data/domain/usecase/invoice/FetchInvoices.kt @@ -10,7 +10,7 @@ package org.mifospay.core.data.domain.usecase.invoice import android.util.Log -import com.mifospay.core.model.entity.Invoice +import com.mifospay.core.model.entity.invoice.Invoice import org.mifospay.core.data.base.UseCase import org.mifospay.core.data.fineract.repository.FineractRepository import rx.Subscriber @@ -28,7 +28,7 @@ class FetchInvoices( ) : UseCase.ResponseValue override fun executeUseCase(requestValues: RequestValues) { - mFineractRepository.fetchInvoices(requestValues.clientId) + mFineractRepository.fetchInvoices(requestValues.clientId.toLong()) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe( @@ -40,6 +40,7 @@ class FetchInvoices( } override fun onNext(invoices: List) { + Log.d("invoice@@@", invoices.toString()) useCaseCallback.onSuccess(ResponseValue(invoices)) } }, diff --git a/core/data/src/main/java/org/mifospay/core/data/fineract/repository/FineractRepository.kt b/core/data/src/main/java/org/mifospay/core/data/fineract/repository/FineractRepository.kt index 2fd2cb82a..89e7e7a53 100644 --- a/core/data/src/main/java/org/mifospay/core/data/fineract/repository/FineractRepository.kt +++ b/core/data/src/main/java/org/mifospay/core/data/fineract/repository/FineractRepository.kt @@ -16,7 +16,6 @@ import com.mifospay.core.model.domain.twofactor.AccessToken import com.mifospay.core.model.domain.twofactor.DeliveryMethod import com.mifospay.core.model.domain.user.NewUser import com.mifospay.core.model.domain.user.User -import com.mifospay.core.model.entity.Invoice import com.mifospay.core.model.entity.Page import com.mifospay.core.model.entity.SearchedEntity import com.mifospay.core.model.entity.TPTResponse @@ -30,6 +29,8 @@ import com.mifospay.core.model.entity.beneficary.BeneficiaryPayload import com.mifospay.core.model.entity.beneficary.BeneficiaryUpdatePayload import com.mifospay.core.model.entity.client.Client import com.mifospay.core.model.entity.client.ClientAccounts +import com.mifospay.core.model.entity.invoice.Invoice +import com.mifospay.core.model.entity.invoice.InvoiceEntity import com.mifospay.core.model.entity.kyc.KYCLevel1Details import com.mifospay.core.model.entity.payload.StandingInstructionPayload import com.mifospay.core.model.entity.payload.TransferPayload @@ -206,24 +207,30 @@ class FineractRepository( ) } - fun addInvoice(clientId: String, invoice: Invoice?): Observable { - return fineractApiManager.invoiceApi.addInvoice(clientId, invoice) + fun addInvoice(clientId: Long, invoice: InvoiceEntity?): Observable { + return Observable.fromCallable { + fineractApiManager.invoiceApi.addInvoice(clientId, invoice) + } } - fun fetchInvoices(clientId: String): Observable> { + fun fetchInvoices(clientId: Long): Observable> { return fineractApiManager.invoiceApi.getInvoices(clientId) } - fun fetchInvoice(clientId: String, invoiceId: String): Observable> { + fun fetchInvoice(clientId: Long, invoiceId: Long): Observable> { return fineractApiManager.invoiceApi.getInvoice(clientId, invoiceId) } - fun editInvoice(clientId: String, invoice: Invoice): Observable { - return fineractApiManager.invoiceApi.updateInvoice(clientId, invoice.id, invoice) + fun editInvoice(clientId: Long, invoice: Invoice): Observable { + return Observable.fromCallable { + fineractApiManager.invoiceApi.updateInvoice(clientId, invoice.id, invoice) + } } - fun deleteInvoice(clientId: String, invoiceId: Int): Observable { - return fineractApiManager.invoiceApi.deleteInvoice(clientId, invoiceId) + fun deleteInvoice(clientId: Long, invoiceId: Long): Observable { + return Observable.fromCallable { + fineractApiManager.invoiceApi.deleteInvoice(clientId, invoiceId) + } } val users: Observable> diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt index 30c643617..b6d35f999 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -28,6 +29,7 @@ fun IconBox( icon: ImageVector, onClick: () -> Unit, modifier: Modifier = Modifier, + tint: Color? = null, ) { OutlinedIconButton( onClick = onClick, @@ -35,10 +37,18 @@ fun IconBox( shape = RoundedCornerShape(12.dp), border = BorderStroke(2.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), ) { - Icon( - imageVector = icon, - contentDescription = icon.name, - ) + if (tint != null) { + Icon( + imageVector = icon, + contentDescription = icon.name, + tint = tint, + ) + } else { + Icon( + imageVector = icon, + contentDescription = icon.name, + ) + } } } diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt index 1dff9c437..f1826e291 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt @@ -23,6 +23,8 @@ fun MifosScaffold( backPress: () -> Unit, modifier: Modifier = Modifier, topBarTitle: Int? = null, + titleColor: Color? = MaterialTheme.colorScheme.onSurface, + iconTint: Color? = null, floatingActionButtonContent: FloatingActionButtonContent? = null, snackbarHost: @Composable () -> Unit = {}, scaffoldContent: @Composable (PaddingValues) -> Unit = {}, @@ -35,6 +37,8 @@ fun MifosScaffold( topBarTitle = topBarTitle, backPress = backPress, actions = actions, + titleColor = titleColor, + iconTint = iconTint, ) } }, diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt index ce4b00068..d85d0ae47 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTopBar.kt @@ -28,18 +28,22 @@ fun MifosTopBar( backPress: () -> Unit, modifier: Modifier = Modifier, actions: @Composable RowScope.() -> Unit = {}, + titleColor: Color? = null, + iconTint: Color? = null, ) { CenterAlignedTopAppBar( title = { Text( text = stringResource(id = topBarTitle), style = MaterialTheme.typography.titleMedium, + color = titleColor ?: MaterialTheme.colorScheme.onSurface, ) }, navigationIcon = { IconBox( icon = MifosIcons.ArrowBack2, onClick = backPress, + tint = iconTint, ) }, colors = TopAppBarDefaults.centerAlignedTopAppBarColors( diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt index 26bb1d2b1..553fe7e3c 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/icon/MifosIcons.kt @@ -16,6 +16,7 @@ import androidx.compose.material.icons.filled.ArrowOutward import androidx.compose.material.icons.filled.AttachMoney import androidx.compose.material.icons.filled.Camera import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.CheckCircleOutline import androidx.compose.material.icons.filled.ChevronLeft import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.Close @@ -30,6 +31,7 @@ import androidx.compose.material.icons.filled.Photo import androidx.compose.material.icons.filled.PhotoLibrary import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.filled.QrCode2 +import androidx.compose.material.icons.filled.RemoveCircleOutline import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff @@ -99,4 +101,6 @@ object MifosIcons { val QrCode2 = Icons.Filled.QrCode2 val Edit = Icons.Filled.Edit val Edit2 = Icons.Outlined.Edit + val CheckCircle = Icons.Default.CheckCircleOutline + val CheckCircle2 = Icons.Default.RemoveCircleOutline } diff --git a/core/model/src/main/java/com/mifospay/core/model/entity/Invoice.kt b/core/model/src/main/java/com/mifospay/core/model/entity/Invoice.kt deleted file mode 100644 index 12fd3d9f0..000000000 --- a/core/model/src/main/java/com/mifospay/core/model/entity/Invoice.kt +++ /dev/null @@ -1,49 +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 com.mifospay.core.model.entity - -import android.os.Parcelable -import com.google.gson.annotations.SerializedName -import kotlinx.parcelize.Parcelize -import kotlinx.parcelize.RawValue - -@Parcelize -data class Invoice( - - @SerializedName("consumerId") - var consumerId: String? = null, - - @SerializedName("consumerName") - var consumerName: String? = null, - - @SerializedName("amount") - var amount: Double = 0.0, - - @SerializedName("itemsBought") - var itemsBought: String? = null, - - @SerializedName("status") - var status: Long = 0L, - - @SerializedName("transactionId") - var transactionId: String? = null, - - @SerializedName("id") - var id: Long = 0L, - - @SerializedName("title") - var title: String? = null, - - @SerializedName("date") - var date: @RawValue MutableList = ArrayList(), - -) : Parcelable { - constructor() : this(null, null, 0.0, null, 0L, null, 0L, null, ArrayList()) -} diff --git a/core/model/src/main/java/com/mifospay/core/model/entity/invoice/Invoice.kt b/core/model/src/main/java/com/mifospay/core/model/entity/invoice/Invoice.kt new file mode 100644 index 000000000..ea7ced4bc --- /dev/null +++ b/core/model/src/main/java/com/mifospay/core/model/entity/invoice/Invoice.kt @@ -0,0 +1,35 @@ +/* + * 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 com.mifospay.core.model.entity.invoice + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Invoice( + val id: Long, + @SerializedName("client_id") + val clientId: Long, + val consumerId: String, + val consumerName: String, + val amount: Double, + @SerializedName("itemsbought") + val itemsBought: String, + val status: Long, + val transactionId: String, + val invoiceId: Long, + val title: String, + val date: String, + @SerializedName("created_at") + val createdAt: List, + @SerializedName("updated_at") + val updatedAt: List, +) : Parcelable diff --git a/core/model/src/main/java/com/mifospay/core/model/entity/invoice/InvoiceEntity.kt b/core/model/src/main/java/com/mifospay/core/model/entity/invoice/InvoiceEntity.kt new file mode 100644 index 000000000..cad568d46 --- /dev/null +++ b/core/model/src/main/java/com/mifospay/core/model/entity/invoice/InvoiceEntity.kt @@ -0,0 +1,30 @@ +/* + * 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 com.mifospay.core.model.entity.invoice + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class InvoiceEntity( + val id: Long, + val invoiceId: Long, + val consumerId: String, + val consumerName: String, + val amount: Double, + @SerialName("itemsbought") + val itemsBought: String, + val status: Long, + val transactionId: String, + val title: String, + val date: String, + val locale: String, + val dateFormat: String, +) diff --git a/core/network/src/main/kotlin/org/mifospay/core/network/MifosWalletOkHttpClient.kt b/core/network/src/main/kotlin/org/mifospay/core/network/MifosWalletOkHttpClient.kt index c58705e70..aaab8f9b5 100644 --- a/core/network/src/main/kotlin/org/mifospay/core/network/MifosWalletOkHttpClient.kt +++ b/core/network/src/main/kotlin/org/mifospay/core/network/MifosWalletOkHttpClient.kt @@ -20,7 +20,12 @@ import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager -class MifosWalletOkHttpClient(private val preferences: PreferencesHelper) { +class MifosWalletOkHttpClient( + private val preferences: PreferencesHelper, + private val username: String? = null, + private val password: String? = null, + private val isTesting: Boolean = false, +) { // Create a trust manager that does not validate certificate chains val mifosOkHttpClient: OkHttpClient // Interceptor :> Full Body Logger and ApiRequest Header @@ -78,7 +83,11 @@ class MifosWalletOkHttpClient(private val preferences: PreferencesHelper) { // Interceptor :> Full Body Logger and ApiRequest Header builder.addInterceptor(logger) - builder.addInterceptor(org.mifospay.core.network.ApiInterceptor(preferences)) + if (isTesting && username != null && password != null) { + builder.addInterceptor(TestingApiInterceptor(username, password)) + } else { + builder.addInterceptor(ApiInterceptor(preferences)) + } return builder.build() } } diff --git a/core/network/src/main/kotlin/org/mifospay/core/network/TestingApiInterceptor.kt b/core/network/src/main/kotlin/org/mifospay/core/network/TestingApiInterceptor.kt new file mode 100644 index 000000000..1592ab43a --- /dev/null +++ b/core/network/src/main/kotlin/org/mifospay/core/network/TestingApiInterceptor.kt @@ -0,0 +1,41 @@ +/* + * 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.core.network + +import okhttp3.Credentials +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +class TestingApiInterceptor( + private val username: String, + private val password: String, +) : Interceptor { + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val chainRequest = chain.request() + + val basicAuthCredentials = Credentials.basic(username, password) + + val builder = chainRequest.newBuilder() + .header(HEADER_TENANT, DEFAULT) + .header(HEADER_AUTH, basicAuthCredentials) + + val request = builder.build() + return chain.proceed(request) + } + + companion object { + const val HEADER_TENANT = "Fineract-Platform-TenantId" + const val HEADER_AUTH = "Authorization" + const val DEFAULT = "venus" + } +} diff --git a/core/network/src/main/kotlin/org/mifospay/core/network/di/NetworkModule.kt b/core/network/src/main/kotlin/org/mifospay/core/network/di/NetworkModule.kt index 639d5623c..41774c371 100644 --- a/core/network/src/main/kotlin/org/mifospay/core/network/di/NetworkModule.kt +++ b/core/network/src/main/kotlin/org/mifospay/core/network/di/NetworkModule.kt @@ -77,6 +77,20 @@ val NetworkModule = module { .build() } + // This can be removed as it for testing purpose + single(Testing) { + val preferencesHelper: PreferencesHelper = get() + Retrofit.Builder() + .baseUrl(BaseURL().url) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .client( + MifosWalletOkHttpClient(preferencesHelper, "mifos", "password", true) + .mifosOkHttpClient, + ) + .build() + } + single { FineractApiManager( authenticationService = get(FineractAuthenticationService), diff --git a/core/network/src/main/kotlin/org/mifospay/core/network/di/Qualifier.kt b/core/network/src/main/kotlin/org/mifospay/core/network/di/Qualifier.kt index ae0471f29..ff398c153 100644 --- a/core/network/src/main/kotlin/org/mifospay/core/network/di/Qualifier.kt +++ b/core/network/src/main/kotlin/org/mifospay/core/network/di/Qualifier.kt @@ -13,7 +13,7 @@ import org.koin.core.qualifier.named val SelfServiceApi = named("SelfServiceApi") val FineractApi = named("FineractApi") - +val Testing = named("Testing") val FineractAuthenticationService = named("FineractAuthenticationService") val FineractClientService = named("FineractClientService") val FineractSavingsAccountsService = named("FineractSavingsAccountsService") diff --git a/core/network/src/main/kotlin/org/mifospay/core/network/services/InvoiceService.kt b/core/network/src/main/kotlin/org/mifospay/core/network/services/InvoiceService.kt index 2a29470c0..45cd13429 100644 --- a/core/network/src/main/kotlin/org/mifospay/core/network/services/InvoiceService.kt +++ b/core/network/src/main/kotlin/org/mifospay/core/network/services/InvoiceService.kt @@ -9,9 +9,9 @@ */ package org.mifospay.core.network.services -import com.mifospay.core.model.entity.Invoice +import com.mifospay.core.model.entity.invoice.Invoice +import com.mifospay.core.model.entity.invoice.InvoiceEntity import org.mifospay.core.network.ApiEndPoints -import org.mifospay.core.network.GenericResponse import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -24,31 +24,31 @@ import rx.Observable * Created by ankur on 07/June/2018 */ interface InvoiceService { - @POST(ApiEndPoints.DATATABLES + "/invoices/{clientId}") + @POST(ApiEndPoints.DATATABLES + "/invoice/{clientId}") fun addInvoice( - @Path("clientId") clientId: String, - @Body invoice: Invoice?, - ): Observable + @Path("clientId") clientId: Long, + @Body invoice: InvoiceEntity?, + ) - @GET(ApiEndPoints.DATATABLES + "/invoices/{clientId}") - fun getInvoices(@Path("clientId") clientId: String): Observable> + @GET(ApiEndPoints.DATATABLES + "/invoice/{clientId}") + fun getInvoices(@Path("clientId") clientId: Long): Observable> - @GET(ApiEndPoints.DATATABLES + "/invoices/{clientId}/{invoiceId}") + @GET(ApiEndPoints.DATATABLES + "/invoice/{clientId}/{invoiceId}") fun getInvoice( - @Path("clientId") clientId: String, - @Path("invoiceId") invoiceId: String, + @Path("clientId") clientId: Long, + @Path("invoiceId") invoiceId: Long, ): Observable> - @DELETE(ApiEndPoints.DATATABLES + "/invoices/{clientId}/{invoiceId}") + @DELETE(ApiEndPoints.DATATABLES + "/invoice/{clientId}/{invoiceId}") fun deleteInvoice( - @Path("clientId") clientId: String, - @Path("invoiceId") invoiceId: Int, - ): Observable + @Path("clientId") clientId: Long, + @Path("invoiceId") invoiceId: Long, + ) - @PUT(ApiEndPoints.DATATABLES + "/invoices/{clientId}/{invoiceId}") + @PUT(ApiEndPoints.DATATABLES + "/invoice/{clientId}/{invoiceId}") fun updateInvoice( - @Path("clientId") clientId: String, + @Path("clientId") clientId: Long, @Path("invoiceId") invoiceId: Long, @Body invoice: Invoice?, - ): Observable + ) } diff --git a/core/ui/src/main/kotlin/org/mifospay/core/ui/AvatarBox.kt b/core/ui/src/main/kotlin/org/mifospay/core/ui/AvatarBox.kt new file mode 100644 index 000000000..3b9749a32 --- /dev/null +++ b/core/ui/src/main/kotlin/org/mifospay/core/ui/AvatarBox.kt @@ -0,0 +1,48 @@ +/* + * 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.core.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +@Composable +fun AvatarBox( + icon: ImageVector, + modifier: Modifier = Modifier, + size: Int = 40, + backgroundColor: Color = MaterialTheme.colorScheme.onPrimary, + contentColor: Color = contentColorFor(backgroundColor), +) { + Box( + modifier = modifier + .size(size.dp) + .clip(CircleShape) + .background(backgroundColor), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = icon, + contentDescription = "Avatar", + tint = contentColor, + ) + } +} diff --git a/core/ui/src/main/kotlin/org/mifospay/core/ui/MifosDivider.kt b/core/ui/src/main/kotlin/org/mifospay/core/ui/MifosDivider.kt new file mode 100644 index 000000000..a46ecbae3 --- /dev/null +++ b/core/ui/src/main/kotlin/org/mifospay/core/ui/MifosDivider.kt @@ -0,0 +1,33 @@ +/* + * 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.core.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.mifospay.core.designsystem.theme.NewUi + +@Composable +fun MifosDivider( + modifier: Modifier = Modifier, + thickness: Dp = 1.dp, + color: Color = NewUi.onSurface.copy(alpha = 0.05f), +) { + HorizontalDivider( + modifier = modifier + .fillMaxWidth(), + thickness = thickness, + color = color, + ) +} diff --git a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceDetailScreen.kt b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceDetailScreen.kt index 5abca0b6d..9d02b4c4b 100644 --- a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceDetailScreen.kt +++ b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceDetailScreen.kt @@ -10,46 +10,43 @@ package org.mifospay.feature.invoices import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.mifospay.core.model.entity.Invoice -import com.mifospay.core.model.utils.DateHelper +import com.mifospay.core.model.entity.invoice.Invoice import org.koin.androidx.compose.koinViewModel import org.mifospay.common.Constants import org.mifospay.core.designsystem.component.MfOverlayLoadingWheel import org.mifospay.core.designsystem.component.MifosScaffold import org.mifospay.core.designsystem.theme.MifosTheme import org.mifospay.core.ui.ErrorScreenContent +import org.mifospay.core.ui.MifosDivider import org.mifospay.invoices.R @Composable internal fun InvoiceDetailScreen( onBackPress: () -> Unit, - navigateToReceiptScreen: (String) -> Unit, modifier: Modifier = Modifier, viewModel: InvoiceDetailViewModel = koinViewModel(), ) { @@ -58,7 +55,6 @@ internal fun InvoiceDetailScreen( InvoiceDetailScreen( invoiceDetailUiState = invoiceDetailUiState, onBackPress = onBackPress, - navigateToReceiptScreen = navigateToReceiptScreen, modifier = modifier, ) } @@ -67,7 +63,6 @@ internal fun InvoiceDetailScreen( private fun InvoiceDetailScreen( invoiceDetailUiState: InvoiceDetailUiState, onBackPress: () -> Unit, - navigateToReceiptScreen: (String) -> Unit, modifier: Modifier = Modifier, ) { MifosScaffold( @@ -93,11 +88,8 @@ private fun InvoiceDetailScreen( } is InvoiceDetailUiState.Success -> { - InvoiceDetailsContent( + InvoiceDetailContent( invoiceDetailUiState.invoice, - invoiceDetailUiState.merchantId, - invoiceDetailUiState.paymentLink, - navigateToReceiptScreen = navigateToReceiptScreen, ) } } @@ -107,209 +99,157 @@ private fun InvoiceDetailScreen( } @Composable -@Suppress("LongMethod") -private fun InvoiceDetailsContent( +private fun InvoiceDetailContent( invoice: Invoice?, - merchantId: String?, - paymentLink: String?, - navigateToReceiptScreen: (String) -> Unit, modifier: Modifier = Modifier, + lazyListState: LazyListState = rememberLazyListState(), ) { - val clipboardManager = LocalClipboardManager.current + LazyColumn( + modifier = modifier.fillMaxSize(), + state = lazyListState, + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { + if (invoice != null) { + InvoiceDetailCard( + invoice = invoice, + modifier = Modifier, + ) + } + } + } +} +@Composable +private fun InvoiceDetailCard( + invoice: Invoice, + modifier: Modifier = Modifier, +) { Column( modifier = modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), + .fillMaxWidth(), ) { - Text( - text = stringResource(R.string.feature_invoices_invoice_details), - modifier = Modifier.padding(top = 16.dp), - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + RowBlock { Text( - text = stringResource(R.string.feature_invoices_merchant_id), - modifier = Modifier.padding(top = 16.dp), + text = stringResource(id = R.string.feature_invoices_merchant_id), + color = + MaterialTheme.colorScheme.onSurface, ) Text( - text = merchantId.toString(), - modifier = Modifier.padding(top = 16.dp), + text = invoice.consumerId, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + RowBlock { Text( text = stringResource(R.string.feature_invoices_consumer_id), - modifier = Modifier.padding(top = 8.dp), + color = MaterialTheme.colorScheme.onSurface, ) Text( - text = (invoice?.consumerName + " " + invoice?.consumerId), - modifier = Modifier.padding(top = 8.dp), + text = invoice.consumerName, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + RowBlock { Text( - text = stringResource(id = R.string.feature_invoices_amount), - modifier = Modifier.padding(top = 8.dp), + text = stringResource(R.string.feature_invoices_amount), + color = MaterialTheme.colorScheme.onSurface, ) Text( - text = Constants.INR + " " + invoice?.amount + "", - modifier = Modifier.padding(top = 8.dp), + text = invoice.amount.toString(), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + RowBlock { Text( - text = stringResource(id = R.string.feature_invoices_items_bought), - modifier = Modifier.padding(top = 8.dp), + text = stringResource(R.string.feature_invoices_items_bought), + color = MaterialTheme.colorScheme.onSurface, ) Text( - text = invoice?.itemsBought.toString(), - modifier = Modifier.padding(top = 8.dp), + text = invoice.itemsBought, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } - if (invoice?.status == 1L) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(id = R.string.feature_invoices_status), - modifier = Modifier.padding(top = 8.dp), - ) - - Text( - text = Constants.DONE, - modifier = Modifier.padding(top = 8.dp), - ) - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(id = R.string.feature_invoices_transaction_id), - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(top = 10.dp), - ) - Text( - text = invoice.transactionId ?: "", - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(top = 10.dp) - .then(Modifier.height(0.dp)), - ) - } - Divider() + RowBlock { Text( - text = stringResource(id = R.string.feature_invoices_unique_receipt_link), - fontWeight = FontWeight.Normal, - modifier = Modifier.padding(bottom = 10.dp), + text = stringResource(R.string.feature_invoices_status), + color = MaterialTheme.colorScheme.onSurface, ) + Text( - text = invoice.transactionId ?: "", - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(top = 10.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - invoice.transactionId?.let { it1 -> - navigateToReceiptScreen.invoke( - it1, - ) - } - }, - onLongPress = { - clipboardManager.setText( - AnnotatedString( - Constants.RECEIPT_DOMAIN + invoice.transactionId, - ), - ) - }, - ) - }, + text = if (invoice.status == 1L) Constants.DONE else Constants.PENDING, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + RowBlock { Text( - text = stringResource(id = R.string.feature_invoices_date), - modifier = Modifier.padding(top = 10.dp), + text = stringResource(R.string.feature_invoices_transaction_id), + color = MaterialTheme.colorScheme.onSurface, ) Text( - text = DateHelper.getDateAsString(invoice!!.date), - modifier = Modifier.padding(top = 10.dp), + text = invoice.transactionId, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } - Divider() - Text( - text = stringResource(id = R.string.feature_invoices_payment_options_will_be_fetched_from_upi), - fontWeight = FontWeight.Normal, - modifier = Modifier.padding(vertical = 10.dp), - ) - Divider() - Text( - text = stringResource(id = R.string.feature_invoices_unique_payment_link), - fontWeight = FontWeight.Normal, - modifier = Modifier.padding(bottom = 10.dp), - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + RowBlock(showDivider = false) { Text( - text = paymentLink.toString(), - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(top = 10.dp) - .pointerInput(Unit) { - detectTapGestures( - onLongPress = { - clipboardManager.setText(AnnotatedString(paymentLink.toString())) - }, - ) - }, + text = stringResource(R.string.feature_invoices_date), + color = MaterialTheme.colorScheme.onSurface, + ) + + Text( + text = invoice.date, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, ) } } } @Composable -private fun Divider(modifier: Modifier = Modifier) { - Box( - modifier = modifier - .fillMaxWidth() - .height(1.dp) - .background(Color(0x44000000)), - ) +private inline fun RowBlock( + modifier: Modifier = Modifier, + showDivider: Boolean = true, + crossinline content: @Composable (RowScope.() -> Unit), +) { + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + content() + } + + if (showDivider) { + MifosDivider() + } + } } class InvoiceDetailScreenProvider : PreviewParameterProvider { @@ -317,7 +257,23 @@ class InvoiceDetailScreenProvider : PreviewParameterProvider(InvoiceDetailUiState.Loading) val invoiceDetailUiState: StateFlow = _invoiceDetailUiState - init { savedStateHandle.get(INVOICE_DATA_ARG)?.let { invoiceData -> Uri.decode(invoiceData)?.let { @@ -43,14 +40,11 @@ class InvoiceDetailViewModel( private fun getInvoiceDetails(data: Uri?) { mUseCaseHandler.execute( fetchInvoiceUseCase, - FetchInvoice.RequestValues(data), + data?.let { FetchInvoice.RequestValues(it) }, object : UseCase.UseCaseCallback { override fun onSuccess(response: FetchInvoice.ResponseValue) { _invoiceDetailUiState.value = InvoiceDetailUiState.Success( response.invoices[0], - mPreferencesHelper.fullName + " " + - mPreferencesHelper.clientId, - data.toString(), ) } @@ -66,8 +60,6 @@ sealed interface InvoiceDetailUiState { data object Loading : InvoiceDetailUiState data class Success( val invoice: Invoice?, - val merchantId: String?, - val paymentLink: String?, ) : InvoiceDetailUiState data class Error(val message: String) : InvoiceDetailUiState diff --git a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceItem.kt b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceItem.kt index de8eba66d..706bb7bc0 100644 --- a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceItem.kt +++ b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceItem.kt @@ -9,134 +9,95 @@ */ package org.mifospay.feature.invoices -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Card +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import org.mifospay.core.designsystem.theme.grey -import org.mifospay.invoices.R +import com.mifospay.core.model.entity.invoice.Invoice +import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.core.ui.AvatarBox @Composable internal fun InvoiceItem( - invoiceTitle: String, - invoiceAmount: String, - invoiceStatus: String, - invoiceDate: String, - invoiceId: String, - invoiceStatusIcon: Long, - onClick: (String) -> Unit, + invoice: Invoice, + onClick: (Long) -> Unit, modifier: Modifier = Modifier, ) { - Card( + OutlinedCard( modifier = modifier - .fillMaxWidth() - .padding(4.dp) - .clickable { onClick(invoiceId) }, - elevation = CardDefaults.cardElevation(4.dp), - colors = CardDefaults.cardColors(Color.White), + .fillMaxWidth(), + onClick = { + onClick(invoice.invoiceId) + }, + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = Color.Transparent, + ), ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource( - id = if (invoiceStatusIcon == 0L) { - R.drawable.feature_invoices_ic_remove_circle_outline_black_24dp - } else { - R.drawable.feature_invoices_ic_check_round_black_24dp - }, - ), - contentDescription = "Invoice Status", - modifier = Modifier - .size(64.dp) - .padding(5.dp), - tint = if (invoiceStatusIcon == 0L) Color.Yellow else Color.Blue, + ListItem( + headlineContent = { + Text(text = invoice.title) + }, + supportingContent = { + Text(text = "${invoice.date} | ${invoice.consumerName}") + }, + leadingContent = { + AvatarBox( + icon = if (invoice.status == 1L) { + MifosIcons.CheckCircle + } else { + MifosIcons.CheckCircle2 + }, + contentColor = if (invoice.status == 1L) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.error + }, ) - Column( - modifier = Modifier - .fillMaxWidth() - .padding(end = 10.dp), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = invoiceTitle, - color = Color.Black, - modifier = Modifier.weight(1f), - ) - Text( - text = invoiceAmount, - color = Color.Black, - textAlign = TextAlign.End, - modifier = Modifier.weight(1f), - ) - } - Text( - text = invoiceStatus, - color = grey, - modifier = Modifier.padding(top = 1.dp), - ) - Row( - modifier = Modifier.fillMaxWidth(), - ) { - Text( - text = invoiceDate, - color = grey, - modifier = Modifier.weight(1f), - ) - Text( - text = invoiceId, - color = grey, - textAlign = TextAlign.End, - modifier = Modifier.weight(1f), - ) - } - Spacer( - modifier = Modifier - .padding(top = 10.dp) - .fillMaxWidth() - .height(1.dp) - .background(Color.Gray), - ) - } - } - } + }, + trailingContent = { + Text( + text = invoice.amount.toString(), + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.titleSmall, + ) + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) } } -@Preview(showBackground = true) +@Preview @Composable private fun PreviewInvoiceItem() { InvoiceItem( - invoiceTitle = "Logo for Richard", - invoiceAmount = "$3000", - invoiceStatus = "Pending", - invoiceDate = "12/3/4", - invoiceId = "Invoice id:12345", - invoiceStatusIcon = 0L, + invoice = Invoice( + id = 1L, + clientId = 2L, + consumerId = "CUST001", + consumerName = "John Doe", + amount = 1500.750000, + itemsBought = "Laptop, Mouse, Keyboard", + status = 1L, + transactionId = "TRX12345", + invoiceId = 1L, + title = "Invoice for Computer Accessories", + date = "19 October 2024", + createdAt = listOf(System.currentTimeMillis()), + updatedAt = listOf(System.currentTimeMillis()), + ), onClick = {}, ) } diff --git a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceScreen.kt b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceScreen.kt index 6768fe3b5..d045e4114 100644 --- a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceScreen.kt +++ b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoiceScreen.kt @@ -10,21 +10,25 @@ package org.mifospay.feature.invoices import android.net.Uri -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.mifospay.core.model.entity.Invoice +import com.mifospay.core.model.entity.invoice.Invoice import org.koin.androidx.compose.koinViewModel import org.mifospay.core.designsystem.component.MifosLoadingWheel import org.mifospay.core.designsystem.icon.MifosIcons.Info @@ -60,34 +64,22 @@ private fun InvoiceScreen( title = stringResource(id = R.string.feature_invoices_error_oops), subTitle = stringResource(id = R.string.feature_invoices_unexpected_error_subtitle), modifier = Modifier, - iconTint = Color.Black, + iconTint = MaterialTheme.colorScheme.primary, iconImageVector = Info, ) } is InvoicesUiState.InvoiceList -> { - Column(modifier = modifier.fillMaxSize()) { - LazyColumn(modifier = Modifier.fillMaxSize()) { - items( - items = invoiceUiState.list, - ) { - InvoiceItem( - invoiceTitle = it?.title.toString(), - invoiceAmount = it?.amount.toString(), - invoiceStatus = it?.status.toString(), - invoiceDate = it?.date.toString(), - invoiceId = it?.id.toString(), - invoiceStatusIcon = it?.status!!, - onClick = { invoiceId -> - val invoiceUri = getUniqueInvoiceLink(invoiceId.toLong()) - invoiceUri?.let { uri -> - navigateToInvoiceDetailScreen.invoke(uri) - } - }, - ) + InvoicesList( + invoiceList = invoiceUiState.list, + modifier = modifier, + onClickInvoice = { invoiceId -> + val invoiceUri = getUniqueInvoiceLink(invoiceId) + invoiceUri?.let { uri -> + navigateToInvoiceDetailScreen.invoke(uri) } - } - } + }, + ) } InvoicesUiState.Empty -> { @@ -95,7 +87,7 @@ private fun InvoiceScreen( title = stringResource(id = R.string.feature_invoices_error_oops), subTitle = stringResource(id = R.string.feature_invoices_error_no_invoices_found), modifier = Modifier, - iconTint = Color.Black, + iconTint = MaterialTheme.colorScheme.onPrimaryContainer, ) } @@ -108,6 +100,33 @@ private fun InvoiceScreen( } } +@Composable +private fun InvoicesList( + invoiceList: List, + onClickInvoice: (Long) -> Unit, + modifier: Modifier = Modifier, + lazyListState: LazyListState = rememberLazyListState(), +) { + LazyColumn( + modifier = modifier.fillMaxSize(), + state = lazyListState, + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + items( + items = invoiceList, + key = { it?.invoiceId ?: 0L }, + ) { invoice -> + if (invoice != null) { + InvoiceItem( + invoice = invoice, + onClick = onClickInvoice, + ) + } + } + } +} + class InvoicesUiStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( @@ -134,14 +153,18 @@ private fun InvoiceScreenPreview( val sampleInvoiceList = List(10) { index -> Invoice( - consumerId = "123456", + id = 1L, + clientId = 2L, + consumerId = "CUST001", consumerName = "John Doe", - amount = 250.75, - itemsBought = "2x Notebook, 1x Pen", + amount = 1500.750000, + itemsBought = "Laptop, Mouse, Keyboard", status = 1L, - transactionId = "txn_78910", - id = index.toLong(), - title = "Stationery Purchase", - date = mutableListOf(2024, 3, 23), + transactionId = "TRX12345", + invoiceId = 1L, + title = "Invoice for Computer Accessories", + date = "19 October 2024", + createdAt = listOf(System.currentTimeMillis()), + updatedAt = listOf(System.currentTimeMillis()), ) } diff --git a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoicesViewModel.kt b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoicesViewModel.kt index 5ef00c24c..d2ce45f87 100644 --- a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoicesViewModel.kt +++ b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/InvoicesViewModel.kt @@ -11,7 +11,7 @@ package org.mifospay.feature.invoices import android.net.Uri import androidx.lifecycle.ViewModel -import com.mifospay.core.model.entity.Invoice +import com.mifospay.core.model.entity.invoice.Invoice import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.mifospay.common.Constants.INVOICE_DOMAIN diff --git a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/di/InvoicesModule.kt b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/di/InvoicesModule.kt index 20543b49e..69ff658e8 100644 --- a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/di/InvoicesModule.kt +++ b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/di/InvoicesModule.kt @@ -9,6 +9,7 @@ */ package org.mifospay.feature.invoices.di +import androidx.lifecycle.SavedStateHandle import org.koin.core.module.dsl.viewModel import org.koin.dsl.module import org.mifospay.feature.invoices.InvoiceDetailViewModel @@ -16,12 +17,11 @@ import org.mifospay.feature.invoices.InvoicesViewModel val InvoicesModule = module { - viewModel { + viewModel { (savedStateHandle: SavedStateHandle) -> InvoiceDetailViewModel( mUseCaseHandler = get(), - mPreferencesHelper = get(), fetchInvoiceUseCase = get(), - savedStateHandle = get(), + savedStateHandle = savedStateHandle, ) } diff --git a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/navigation/InvoiceNavigation.kt b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/navigation/InvoiceNavigation.kt index bb5afe0f3..d3d7cbd71 100644 --- a/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/navigation/InvoiceNavigation.kt +++ b/feature/invoices/src/main/kotlin/org/mifospay/feature/invoices/navigation/InvoiceNavigation.kt @@ -25,7 +25,6 @@ fun NavController.navigateToInvoiceDetail(invoiceData: String) { } fun NavGraphBuilder.invoiceDetailScreen( - navigateToReceiptScreen: (String) -> Unit, onBackPress: () -> Unit, ) { composable( @@ -36,7 +35,6 @@ fun NavGraphBuilder.invoiceDetailScreen( ) { InvoiceDetailScreen( onBackPress = onBackPress, - navigateToReceiptScreen = navigateToReceiptScreen, ) } } diff --git a/feature/invoices/src/main/res/values/strings.xml b/feature/invoices/src/main/res/values/strings.xml index bd8507193..da93e7ca1 100644 --- a/feature/invoices/src/main/res/values/strings.xml +++ b/feature/invoices/src/main/res/values/strings.xml @@ -12,14 +12,14 @@ Invoice Loading Invoice details - Merchant ID - Consumer ID + Consumer ID + Consumer Name Amount - items bought - status + Items Bought + Status Transaction ID : Unique Receipt Link : - date + Date Payment Options (will be fetched from UPI) diff --git a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/GenerateQr.kt b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/GenerateQr.kt index 30457707a..bec299b65 100644 --- a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/GenerateQr.kt +++ b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/GenerateQr.kt @@ -9,35 +9,42 @@ */ package org.mifospay.feature.request.money +import android.content.Context import android.graphics.Bitmap -import com.google.zxing.BarcodeFormat -import com.google.zxing.MultiFormatWriter +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.RectF +import androidx.core.content.ContextCompat +import com.google.zxing.EncodeHintType import com.google.zxing.WriterException -import com.google.zxing.common.BitMatrix -import org.mifospay.common.Constants +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.google.zxing.qrcode.encoder.ByteMatrix +import com.google.zxing.qrcode.encoder.Encoder +import com.google.zxing.qrcode.encoder.QRCode import org.mifospay.core.data.base.UseCase import java.util.Base64 +import java.util.EnumMap -/** - * Created by naman on 8/7/17. - */ -class GenerateQr : - UseCase() { +// Taken reference from this article +// https://ihareshvaghela.medium.com/generating-dotted-qr-codes-in-android-using-zxing-library-b02e824c895c + +class GenerateQr(private val context: Context) : UseCase< + GenerateQr.RequestValues, + GenerateQr + .ResponseValue?, + >() { override fun executeUseCase(requestValues: RequestValues) { try { - val bitmap = encodeAsBitmap(makeUpiString(requestValues.data)) - if (bitmap != null) { - useCaseCallback.onSuccess(ResponseValue(bitmap)) - } else { - useCaseCallback.onError(Constants.ERROR_OCCURRED) - } + val bitmap = encodeAsBitmap(makeUpiString(requestValues.data), getLogoBitmap()) + useCaseCallback.onSuccess(ResponseValue(bitmap)) } catch (e: WriterException) { - useCaseCallback.onError(Constants.FAILED_TO_WRITE_DATA_TO_QR) + useCaseCallback.onError("Failed to write data to QR") } } private fun makeUpiString(requestQrData: RequestQrData): String { - // Initial payment string val requestPaymentString = "upi://pay" + "?pa=${requestQrData.vpaId}" + "&am=${requestQrData.amount}" + @@ -45,50 +52,233 @@ class GenerateQr : "&cu=${requestQrData.currency}" + "&mode=02" + "&s=000000" - - // Convert the payment string to bytes and encode to Base64 val sign = Base64.getEncoder().encodeToString(requestPaymentString.toByteArray(Charsets.UTF_8)) + return "$requestPaymentString&sign=$sign" + } + + @Throws(WriterException::class) + private fun encodeAsBitmap(str: String, logo: Bitmap?): Bitmap { + val encodingHints: MutableMap = + EnumMap(com.google.zxing.EncodeHintType::class.java) + encodingHints[EncodeHintType.CHARACTER_SET] = "UTF-8" + val code: QRCode = Encoder.encode(str, ErrorCorrectionLevel.H, encodingHints) + return renderQRImage(code, logo) + } + + // Function to render QR code + private fun renderQRImage(code: QRCode, logo: Bitmap?): Bitmap { + val bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.FILL + color = BLUE + } + + val input = code.matrix + val inputWidth = input.width + val inputHeight = input.height + val qrWidth = inputWidth + (QUIET_ZONE * 2) + val qrHeight = inputHeight + (QUIET_ZONE * 2) + val outputWidth = WIDTH.coerceAtLeast(qrWidth) + val outputHeight = HEIGHT.coerceAtLeast(qrHeight) + val multiple = (outputWidth / qrWidth).coerceAtMost(outputHeight / qrHeight) + val leftPadding = (outputWidth - (inputWidth * multiple)) / 2 + val topPadding = (outputHeight - (inputHeight * multiple)) / 2 + + drawQrCodeDots( + input, + canvas, + paint, + leftPadding, + topPadding, + inputWidth, + inputHeight, + multiple, + ) - val signedRequestPayment = requestPaymentString + - "&sign=$sign" + val circleDiameter = multiple * FINDER_PATTERN_SIZE + drawFinderPatternSquareStyle(canvas, paint, leftPadding, topPadding, circleDiameter) + drawFinderPatternSquareStyle( + canvas, + paint, + leftPadding + (inputWidth - FINDER_PATTERN_SIZE) * multiple, + topPadding, + circleDiameter, + ) + drawFinderPatternSquareStyle( + canvas, + paint, + leftPadding, + topPadding + (inputHeight - FINDER_PATTERN_SIZE) * multiple, + circleDiameter, + ) + + logo?.let { + drawLogo(canvas, bitmap, it) + } - // Convert the final URI to string - return signedRequestPayment + return bitmap } - @Throws(WriterException::class) - private fun encodeAsBitmap(str: String): Bitmap? { - val result: BitMatrix = try { - MultiFormatWriter().encode( - str, - BarcodeFormat.QR_CODE, - WIDTH, - WIDTH, - null, + private fun drawQrCodeDots( + input: ByteMatrix, + canvas: Canvas, + paint: Paint, + leftPadding: Int, + topPadding: Int, + inputWidth: Int, + inputHeight: Int, + multiple: Int, + ) { + val circleSize = (multiple * CIRCLE_SCALE_DOWN_FACTOR).toInt() + for (inputY in 0 until inputHeight) { + var outputY = topPadding + outputY += multiple * inputY + for (inputX in 0 until inputWidth) { + var outputX = leftPadding + outputX += multiple * inputX + if (input.get(inputX, inputY).toInt() == 1 && + !isFinderPattern(inputX, inputY, inputWidth, inputHeight) + ) { + canvas.drawCircle( + (outputX + multiple / 2).toFloat(), + (outputY + multiple / 2).toFloat(), + circleSize.toFloat() / 2f, + paint, + ) + } + } + } + } + + private fun isFinderPattern( + inputX: Int, + inputY: Int, + inputWidth: Int, + inputHeight: Int, + ): Boolean { + return ( + inputX <= FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE || + inputX >= inputWidth - FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE || + inputX <= FINDER_PATTERN_SIZE && inputY >= inputHeight - FINDER_PATTERN_SIZE ) - } catch (iae: IllegalArgumentException) { - return null + } + + private fun drawLogo(canvas: Canvas, bitmap: Bitmap, logo: Bitmap) { + val logoSize = (WIDTH * 0.11).toInt() + val centerX = (bitmap.width - logoSize) / 2 + val centerY = (bitmap.height - logoSize) / 2 + val backgroundRadius = (logoSize * 0.75).toFloat() + + val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.FILL + color = ELLIPSE_COLOR } - val w = result.width - val h = result.height - val pixels = IntArray(w * h) - for (y in 0 until h) { - val offset = y * w - for (x in 0 until w) { - pixels[offset + x] = if (result[x, y]) BLACK else WHITE - } + + // Draw logo background + canvas.drawCircle( + (centerX + logoSize / 2).toFloat(), + (centerY + logoSize / 2).toFloat(), + backgroundRadius + 10f, + paint, + ) + + paint.color = Color.WHITE + canvas.drawCircle( + (centerX + logoSize / 2).toFloat(), + (centerY + logoSize / 2).toFloat(), + backgroundRadius + 6f, + paint, + ) + + // Draw the logo + val logoRect = Rect(0, 0, logo.width, logo.height) + val destRect = Rect(centerX, centerY, centerX + logoSize, centerY + logoSize) + canvas.drawBitmap(logo, logoRect, destRect, null) + } + + // Function to draw a finder pattern + private fun drawFinderPatternSquareStyle( + canvas: Canvas, + paint: Paint, + x: Int, + y: Int, + squareDiameter: Int, + ) { + val outerRadius = squareDiameter * 0.25f + val middleRadius = squareDiameter * 0.15f + val innerRadius = squareDiameter * 0.1f + val middleSquareScale = 0.7f + val innerSquareScale = 0.45f + + paint.color = PATTERN_COLOR + canvas.drawRoundRect( + RectF( + x.toFloat(), + y.toFloat(), + (x + squareDiameter).toFloat(), + (y + squareDiameter).toFloat(), + ), + outerRadius, + outerRadius, + paint, + ) + + val middleSquareSize = squareDiameter * middleSquareScale + val middleSquareOffset = (squareDiameter - middleSquareSize) / 2 + paint.color = Color.WHITE + canvas.drawRoundRect( + RectF( + (x + middleSquareOffset), + (y + middleSquareOffset), + (x + middleSquareOffset + middleSquareSize), + (y + middleSquareOffset + middleSquareSize), + ), + middleRadius, + middleRadius, + paint, + ) + + val innerSquareSize = squareDiameter * innerSquareScale + val innerSquareOffset = (squareDiameter - innerSquareSize) / 2 + paint.color = PATTERN_COLOR + canvas.drawRoundRect( + RectF( + (x + innerSquareOffset), + (y + innerSquareOffset), + (x + innerSquareOffset + innerSquareSize), + (y + innerSquareOffset + innerSquareSize), + ), + innerRadius, + innerRadius, + paint, + ) + } + + // Function to get logo in the center of QR code + private fun getLogoBitmap(): Bitmap? { + val drawable = ContextCompat.getDrawable(context, R.drawable.logo) + return drawable?.let { + val bitmap = + Bitmap.createBitmap(it.intrinsicWidth, it.intrinsicHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + it.setBounds(0, 0, canvas.width, canvas.height) + it.draw(canvas) + bitmap } - val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) - bitmap.setPixels(pixels, 0, WIDTH, 0, 0, w, h) - return bitmap } - class RequestValues(val data: RequestQrData) : UseCase.RequestValues - class ResponseValue(val bitmap: Bitmap) : UseCase.ResponseValue + data class RequestValues(val data: RequestQrData) : UseCase.RequestValues + data class ResponseValue(val bitmap: Bitmap) : UseCase.ResponseValue companion object { - private const val WHITE = -0x1 - private const val BLACK = -0x1000000 - private const val WIDTH = 500 + private const val BLUE = 0xFF0673BA.toInt() // dots color + private const val PATTERN_COLOR = 0xFF6e6e6e.toInt() // corner square color + private const val ELLIPSE_COLOR = 0xFFe9e9e9.toInt() // logo background ellipse color + private const val WIDTH = 500 // width of QR code + private const val HEIGHT = 500 // height of QR code + private const val FINDER_PATTERN_SIZE = 13 // pattern size (in this case corner squares) + private const val CIRCLE_SCALE_DOWN_FACTOR = 1f // size of dots in qr code + private const val QUIET_ZONE = 5 // spacing from all sides for QR code } } diff --git a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrContent.kt b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrContent.kt index ed0dc99ff..3c65d24bd 100644 --- a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrContent.kt +++ b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrContent.kt @@ -10,94 +10,112 @@ package org.mifospay.feature.request.money import android.graphics.Bitmap +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.HorizontalDivider +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import org.mifospay.core.designsystem.component.MifosButton -import java.util.Locale +import org.mifospay.core.designsystem.component.MifosOutlinedButton +import org.mifospay.core.designsystem.theme.MifosBlue @Composable internal fun ShowQrContent( qrDataBitmap: Bitmap, - qrDataString: String, showAmountDialog: () -> Unit, modifier: Modifier = Modifier, - amount: String? = null, + onShare: () -> Unit, ) { - Column( - modifier = modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, + Box( + modifier = modifier + .fillMaxSize() + .background( + MifosBlue, + ) + .padding(25.dp), ) { - AsyncImage( - model = qrDataBitmap, - contentDescription = null, - modifier = Modifier - .padding(20.dp) - .weight(1f), - ) - - Spacer(modifier = Modifier.height(12.dp)) - - MifosButton( - modifier = Modifier - .width(150.dp), - onClick = { showAmountDialog() }, - ) { - Text(text = stringResource(id = R.string.feature_request_money_set_amount)) - } - - Spacer(modifier = Modifier.height(12.dp)) - - HorizontalDivider() - Column( modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - horizontalAlignment = Alignment.Start, + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally, ) { - Text( - text = stringResource(id = R.string.feature_request_money_qr_code_details), - style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold), - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = when (amount) { - null -> String.format( - Locale.getDefault(), - format = "%s: %s", - stringResource(R.string.feature_request_money_vpa), - qrDataString, + Spacer(modifier = Modifier.weight(1f)) + Column( + modifier = Modifier + .clip(RoundedCornerShape(21.dp)) + .background(Color.White) + .border( + BorderStroke(2.dp, MaterialTheme.colorScheme.primary), + RoundedCornerShape(21.dp), ) - - else -> String.format( - Locale.getDefault(), - format = "%s: %s\n%s: %s", - stringResource(R.string.feature_request_money_vpa), - qrDataString, - stringResource( - R.string.feature_request_money_amount, - ), - amount, + .padding(vertical = 15.dp) + .aspectRatio(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(id = R.string.feature_request_money_title), + color = MifosBlue, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + ) + AsyncImage( + model = qrDataBitmap, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + ) + } + Spacer(modifier = Modifier.weight(1f)) + Column( + verticalArrangement = Arrangement.spacedBy(15.dp), + ) { + MifosButton( + modifier = Modifier + .fillMaxWidth() + .height(55.dp), + onClick = { showAmountDialog() }, + color = Color.White, + ) { + Text( + text = stringResource(id = R.string.feature_request_money_set_amount), + color = MifosBlue, ) - }, - style = MaterialTheme.typography.bodyMedium, - ) + } + MifosOutlinedButton( + modifier = Modifier + .fillMaxWidth() + .height(55.dp), + onClick = { onShare() }, + border = BorderStroke( + 1.dp, + Color.White.copy(alpha = 0.3f), + ), + ) { + Text( + text = stringResource(id = R.string.feature_request_money_share), + color = Color.White, + ) + } + } } } } diff --git a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrScreenRoute.kt b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrScreenRoute.kt index 4c539cfcc..f43a0902e 100644 --- a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrScreenRoute.kt +++ b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/ShowQrScreenRoute.kt @@ -15,12 +15,12 @@ import android.content.Intent import android.content.Intent.createChooser import android.graphics.Bitmap import android.net.Uri +import android.util.Log import android.view.WindowManager import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -29,6 +29,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -40,6 +41,7 @@ import org.koin.androidx.compose.koinViewModel import org.mifospay.core.designsystem.component.MfLoadingWheel import org.mifospay.core.designsystem.component.MifosScaffold import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.core.designsystem.theme.MifosBlue import org.mifospay.core.ui.EmptyContentScreen import org.mifospay.feature.request.money.util.ImageUtils @@ -50,13 +52,11 @@ internal fun ShowQrScreenRoute( viewModel: ShowQrViewModel = koinViewModel(), ) { val uiState by viewModel.showQrUiState.collectAsStateWithLifecycle() - val vpaId by viewModel.vpaId.collectAsStateWithLifecycle() UpdateBrightness() ShowQrScreen( uiState = uiState, - vpaId = vpaId, backPress = backPress, generateQR = viewModel::generateQr, modifier = modifier, @@ -67,7 +67,6 @@ internal fun ShowQrScreenRoute( @VisibleForTesting internal fun ShowQrScreen( uiState: ShowQrUiState, - vpaId: String, backPress: () -> Unit, generateQR: (RequestQrData) -> Unit, modifier: Modifier = Modifier, @@ -81,6 +80,8 @@ internal fun ShowQrScreen( MifosScaffold( topBarTitle = R.string.feature_request_money_request, backPress = backPress, + titleColor = Color.White, + iconTint = Color.White, scaffoldContent = { paddingValues -> Box(modifier = Modifier.padding(paddingValues)) { when (uiState) { @@ -103,10 +104,15 @@ internal fun ShowQrScreen( } else { qrBitmap = uiState.qrDataBitmap ShowQrContent( - qrDataString = vpaId, - amount = amount, qrDataBitmap = uiState.qrDataBitmap, showAmountDialog = { amountDialogState = true }, + onShare = { + qrBitmap?.let { + Log.d("yesyesyes", it.toString()) + val uri = ImageUtils.saveImage(context = context, bitmap = it) + shareQr(context, uri = uri) + } + }, ) } } @@ -123,17 +129,7 @@ internal fun ShowQrScreen( } } }, - actions = { - IconButton( - onClick = { - qrBitmap?.let { - val uri = ImageUtils.saveImage(context = context, bitmap = it) - shareQr(context, uri = uri) - } - }, - ) { Icon(MifosIcons.Share, null) } - }, - modifier = modifier, + modifier = modifier.background(MifosBlue), ) if (amountDialogState) { @@ -209,7 +205,6 @@ private fun ShowQrScreenPreview( ) { ShowQrScreen( uiState = uiState, - vpaId = "", backPress = {}, generateQR = {}, ) diff --git a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/di/RequestMoneyModule.kt b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/di/RequestMoneyModule.kt index fe0f86e83..d4ce71ecc 100644 --- a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/di/RequestMoneyModule.kt +++ b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/di/RequestMoneyModule.kt @@ -9,6 +9,7 @@ */ package org.mifospay.feature.request.money.di +import org.koin.android.ext.koin.androidContext import org.koin.core.module.dsl.viewModel import org.koin.dsl.module import org.mifospay.feature.request.money.GenerateQr @@ -16,7 +17,7 @@ import org.mifospay.feature.request.money.ShowQrViewModel val RequestMoneyModule = module { single { - GenerateQr() + GenerateQr(context = androidContext()) } viewModel { diff --git a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/util/ImageUtils.kt b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/util/ImageUtils.kt index 4bbff94d8..f014b0b6d 100644 --- a/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/util/ImageUtils.kt +++ b/feature/request-money/src/main/kotlin/org/mifospay/feature/request/money/util/ImageUtils.kt @@ -14,7 +14,6 @@ import android.graphics.Bitmap import android.net.Uri import android.util.Log import androidx.core.content.FileProvider -import org.mifospay.feature.request.money.BuildConfig import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -32,7 +31,7 @@ object ImageUtils { stream.close() uri = FileProvider.getUriForFile( context, - BuildConfig.LIBRARY_PACKAGE_NAME + ".provider", file, + context.packageName + ".provider", file, ) } catch (e: IOException) { Log.d("Error", e.message.toString()) diff --git a/feature/request-money/src/main/res/drawable/logo.png b/feature/request-money/src/main/res/drawable/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b8b006ca36ae14c276fdef8f4221672af61898e2 GIT binary patch literal 2459 zcmV;M31s$(P)7se(ePwmt9UP+Wi)n^SXg zY#WVjniMe#NMvzKTtK67X%rNL0!fSu1_d6z_r7nYcRmsE3A_j7kDmTGyqRz2&Yj=A zbLY-{AHe@O2y_EhZl(1lj^RXSA(AZh0>&+(oda(mKyMl{eAOddJ!d6W9l8h^I*FyF zs|IK~`6&lB(SFDoBM}k+R07UK?>&17lEhz~A8?d7UczcQbP-;AaK*uu;e?XK!1)3K zMDw7u9ym#}=t_n)>Ot=X=fp2CC{$L>+*JbK6~UB})i2`mZA7N=7| zQyJ$xHSqSpD3e^`5sREu@H$~+b5D>Dn=%Y+_}g1ZV~obiR4PG_F2z+L?W=7AV~-zK z8Y=t!2`Cvk@bfR?4_Kr0-<7OdXc+t*B6L5DT5NoIU1VC8X;5PR2q4O4)IJtHwak?{ z?axDZ{$wJfaHyT}o0|Y$xstHU?%wf`2g9;HSM8Vk{T<^i88yXS`N7bYqJ3>d+7>L< zJfxbCGBEVXcgb-gCl)Q|L^&eJ;q#Y%M$@}OjLRgo9dZJTEQD|Co0HJfBSA*}Z5+tY z@6Qjv58d`{)D|fH*2ocF2T_3npe>Bm)(Y{NPBZ=R?UcQ^R^ilA9V>%mQOgz zCl+H?T6Ee~Shg!z(b)k^E{j`F37l>Ud{WMGAuEFyT!k*VkR1arjV_ z76K92?=3k2RR-CHOC2Fr%v|E(Syho3z*9Yq#M7b4LR}3l4e7BJXw+}8{i?exk(Is8 zV-leA7=PT?zED(5f|Sxk@DwAQe}hImb7s!t*Oa8<(MocWslb^LlO+)z0fcXFC0ye= z_VFo`D2GVs0x-sZX_n$AbbHOKNZ?Iqmm4rbj2Y$64k`5cZSiAVC7)np5a!n{Dz&`U zkTL0+kh3K1#aw5s=Zx1oZE@3wei+-I%j#Fqg)?7(%5;qB!SXqe{sn1lZ%+`dSSQGV zb-xc&`1^vK*-rRE7q^$Hi3Rh;Y0zYQ>WCvFHqxp3&NmICmXTd0Ctx^@8Pq%08>hR8jY-cl`mEd7_)h5JIK*)D?S_Bli=L=+(zB`tE+R0u7&NWna&Kp6U^La!UYnKD4!j4LP^{^fl!7= z6(}7k#Z7TiQ~hJ=*M!%)_l+!!^}EwR!(5s)kQ&kejGRH3EFZyB%hwbzuWbA_uFWkh z#m1`^?w??hm3653`Ew+2_wcOhmB>8P6y@;O^GFWnX5oE-ZvqeG1kixdlsnC-LjPU4 zoJ-PGozc=S<^^6n6IaxT&7VRifCPDe3S?n$-ptdLkhkH&yvJ-I+0}iJ@M42f^X%0E z`}`WW)hI7@-ng|UvJTc_O;Y0VbeW=1jvKN1EHHKf^F|ql(|<7dZ=w}Vcm=A(l*I21 zRv7nj8TuoV#CtKW3=m6vud~SjBfctO_dXFvq1&p*l+Q!&y|k;h=Bu5={vdDIVFaGS z)H7PtL$j-oV=)%G0kMm}ZLV7+F1;U|Gh>|CG$uLGXwhEqmRvJhHUh?1Z|Ei{?ymDK zPXzh{p}U6UObcn5gpljlQSYrnOw70Mt0O9O12}gLMh7o*YwvS^69e4IjDA<5`V_35 z^G+*ki6`Dof=KQHdJs;z&G5;kS-GZHwZy`iV6{?v`n1m+M#ChWF$3M=^mYQ2bwD?O z$@wrf3^^A~Hd-2iZZgTG>iSo4lZ2>afBvTz-`2nIylPq|6(QbK+J##5(azxZt4S)Zh$pWzn&hZmY^@P8*G>^)a{>@kr)TX%+KUmmS z@RE5D3FJ5!zIJYis)O$UZQRv3&Qm<5vmd@NUY@`qPhf4aLS2h+`))}&vU&m-MhuZ@ z1!Js^>byZGdL6T~Z@9);!1VyTb)MBZH_NIH8dm3PskbO=ezfL##{|V9C6fv)hd~#C zC&=Tba+y2vCX>ny(MEye8?Pk(%(rIF?Sq_mQVy=f{oquf!S^%GSDopui?-nk?&u*u zhtaeZx(q&C{07mWv$*u_Bx@qR(OIw%X{n&maVy?B&Uynp5)`mTHQez(ya>ZM1(n#L zlRyuKi*m`hB`!?%hdU5;q3r9v0qWe1(Hm>q*5v_yD|V06ne-M~Itjc6`X8@=4qNnv z$92R#d@Cc6SkejcYcsoD- zKo6h}!k@qNbw#8nasuAIadAgxTrrl#eo6aA8NlWs;W#|j#gO#=bdj%%YpxQ Z{1?^xtODE8m0kb<002ovPDHLkV1ii9tl|Iw literal 0 HcmV?d00001 diff --git a/feature/request-money/src/main/res/values/strings.xml b/feature/request-money/src/main/res/values/strings.xml index fc016a955..d14588a7c 100644 --- a/feature/request-money/src/main/res/values/strings.xml +++ b/feature/request-money/src/main/res/values/strings.xml @@ -12,6 +12,8 @@ Please enter a valid amount Enter Currency Set Amount + Mifos Pay + Share Set Currency Cancel Confirm diff --git a/mifospay/src/main/java/org/mifospay/navigation/MifosNavHost.kt b/mifospay/src/main/java/org/mifospay/navigation/MifosNavHost.kt index 0e06932db..745f43a20 100644 --- a/mifospay/src/main/java/org/mifospay/navigation/MifosNavHost.kt +++ b/mifospay/src/main/java/org/mifospay/navigation/MifosNavHost.kt @@ -244,9 +244,9 @@ internal fun MifosNavHost( ) invoiceDetailScreen( onBackPress = navController::popBackStack, - navigateToReceiptScreen = { uri -> - navController.navigateToReceipt(Uri.parse(Constants.RECEIPT_DOMAIN + uri)) - }, +// navigateToReceiptScreen = { uri -> +// navController.navigateToReceipt(Uri.parse(Constants.RECEIPT_DOMAIN + uri)) +// }, ) receiptScreen( openPassCodeActivity = { diff --git a/mifospay/src/test/java/org/mifospay/KoinModulesCheck.kt b/mifospay/src/test/java/org/mifospay/KoinModulesCheck.kt index ea8102b25..0c69be164 100644 --- a/mifospay/src/test/java/org/mifospay/KoinModulesCheck.kt +++ b/mifospay/src/test/java/org/mifospay/KoinModulesCheck.kt @@ -48,6 +48,7 @@ class KoinModulesCheck : AutoCloseKoinTest() { SelfServiceApiManager::class, KtorAuthenticationService::class, SavedStateHandle::class, + Context::class, ), ) koinModules.coreDataStoreModules.verify(