diff --git a/README.md b/README.md index 2396869e5..cc83fca94 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,63 @@ Mobile Wallet is an Android-based framework for mobile wallets based on top of < clean architecture and contains a core library module that can be used as a dependency in any other wallet based project. It is developed at MIFOS together with a global community. +## KMP Status for modules + +| Module | Progress | Desktop supported | Android supported | iOS supported | Web supported(JS) | Web supported(WASM-JS) | +|-------------------------------|-------------------|-------------------|--------------------|-------------------|-------------------|------------------------| +| mifospay-android | In progress | ✔️ | ✔️ | ✔️ | ✔️ | ❔ | +| mifospay-desktop | In progress | ✔️ | ✔️ | ✔️ | ✔️ | ❔ | +| mifospay-web | In progress | ✔️ | ✔️ | ✔️ | ✔️ | ❔ | +| mifospay-ios | No implementation | No implementation | No implementation | No implementation | No implementation | No implementation | +| :core:analytics | Done | ❌ | ✔️ | ❌ | ❌ | ❌ | +| :core:common | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:data | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:datastore | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:datastore-proto | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:designsystem | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:domain | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:model | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:network | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :core:ui | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:auth | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:editpassword | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:faq | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:history | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:home | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:profile | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:settings | Done | ✅ | ✅ | ❔ | ✅ | ❔ | +| :feature:payments | Done | ✅ | ✅ | ✅ | ✅ | ❔ | +| :feature:account | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:finance | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:invoices | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:kyc | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:make-transfer | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:merchants | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:notification | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:qr | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:receipt | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:request-money | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:saved-cards | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:search | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:send-money | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:standing-instruction | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| :feature:upi-setup | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | +| lint | Not started | ❌ | ❌ | ❌ | ❌ | ❌ | + +✅: Functioning properly +❔: Not yet tested, but expected to work +✔️: Successfully compiled +❌: Not functioning, requires further attention + ## Notice :warning: We are fully committed to implement [Jetpack Compose](https://developer.android.com/jetpack/compose) and moving ourself to support -`kotlin multi-platform`. **If you are sending any PR regarding `XML changes` we will `not` consider at this moment but converting XML to jetpack compose are most welcome.** If you sending any PR regarding logical changes in Activity/Fragment you are most welcome. - +`kotlin multi-platform`. **If you are sending any PR regarding `XML changes` we will `not` consider at this moment but converting XML to jetpack compose are most welcome.** If you sending any PR regarding logical changes in Activity/Fragment you are most welcome. -Development | Chat | -|-----------------|-----------------| -![Mobile-Wallet CI[Master/Dev]](https://github.com/openMF/mobile-wallet/workflows/Mobile-Wallet%20CI%5BMaster/Dev%5D/badge.svg?branch=dev) | [![Join the chat at https://mifos.slack.com/](https://img.shields.io/badge/Join%20Our%20Community-Slack-blue)](https://mifos.slack.com/) | +| Development | Chat | +|--------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| ![Mobile-Wallet CI[Master/Dev]](https://github.com/openMF/mobile-wallet/workflows/Mobile-Wallet%20CI%5BMaster/Dev%5D/badge.svg?branch=dev) | [![Join the chat at https://mifos.slack.com/](https://img.shields.io/badge/Join%20Our%20Community-Slack-blue)](https://mifos.slack.com/) | ## Join Us on Slack diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt index 0aac6d9e6..cdd34932b 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt @@ -9,12 +9,18 @@ */ package org.mifospay.core.designsystem.component +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Tab import androidx.compose.material3.Text +import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp @Composable fun MifosTab( @@ -22,20 +28,20 @@ fun MifosTab( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, - selectedContentColor: Color = MaterialTheme.colorScheme.onSurface, - unselectedContentColor: Color = Color.LightGray, + selectedColor: Color = MaterialTheme.colorScheme.primary, + unselectedColor: Color = MaterialTheme.colorScheme.primaryContainer, ) { Tab( text = { - Text( - text = text, - color = MaterialTheme.colorScheme.onSurface, - ) + Text(text = text) }, selected = selected, - modifier = modifier, - selectedContentColor = selectedContentColor, - unselectedContentColor = unselectedContentColor, onClick = onClick, + selectedContentColor = contentColorFor(selectedColor), + unselectedContentColor = contentColorFor(unselectedColor), + modifier = modifier + .clip(RoundedCornerShape(25.dp)) + .background(if (selected) selectedColor else unselectedColor) + .padding(horizontal = 20.dp), ) } diff --git a/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt index b668cc682..467cc5720 100644 --- a/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt +++ b/core/ui/src/commonMain/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt @@ -9,7 +9,6 @@ */ package org.mifospay.core.ui -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.material3.MaterialTheme @@ -24,16 +23,15 @@ import kotlinx.coroutines.launch import org.mifospay.core.designsystem.component.MifosTab import org.mifospay.core.ui.utility.TabContent -@OptIn(ExperimentalFoundationApi::class) @Suppress("MultipleEmitters") @Composable fun MifosScrollableTabRow( tabContents: List, pagerState: PagerState, modifier: Modifier = Modifier, - containerColor: Color = MaterialTheme.colorScheme.surface, - selectedContentColor: Color = MaterialTheme.colorScheme.onSurface, - unselectedContentColor: Color = Color.LightGray, + containerColor: Color = MaterialTheme.colorScheme.primaryContainer, + selectedContentColor: Color = MaterialTheme.colorScheme.primary, + unselectedContentColor: Color = MaterialTheme.colorScheme.primaryContainer, edgePadding: Dp = 8.dp, ) { val scope = rememberCoroutineScope() @@ -43,13 +41,15 @@ fun MifosScrollableTabRow( containerColor = containerColor, selectedTabIndex = pagerState.currentPage, edgePadding = edgePadding, + indicator = {}, + divider = {}, ) { tabContents.forEachIndexed { index, currentTab -> MifosTab( text = currentTab.tabName, selected = pagerState.currentPage == index, - selectedContentColor = selectedContentColor, - unselectedContentColor = unselectedContentColor, + selectedColor = selectedContentColor, + unselectedColor = unselectedContentColor, onClick = { scope.launch { pagerState.animateScrollToPage(index) diff --git a/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt b/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt index 3335634e5..03272c9ae 100644 --- a/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt +++ b/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -37,7 +38,7 @@ import org.mifospay.feature.history.components.HistoryScreenFilter import org.mifospay.feature.history.components.TransactionList @Composable -internal fun HistoryScreen( +fun HistoryScreen( viewTransferDetail: (Long) -> Unit, modifier: Modifier = Modifier, viewModel: HistoryViewModel = koinViewModel(), @@ -125,6 +126,7 @@ private fun HistoryScreenContent( HistoryScreenFilter( selectedTransactionType = selectedTransactionType, onAction = onAction, + modifier = Modifier.padding(top = 8.dp), ) TransactionList( diff --git a/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt b/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt index 1d295fc34..a6c7c4d6a 100644 --- a/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt +++ b/feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt @@ -22,7 +22,7 @@ import org.mifospay.core.model.savingsaccount.Transaction import org.mifospay.core.model.savingsaccount.TransactionType import org.mifospay.core.ui.utils.BaseViewModel -internal class HistoryViewModel( +class HistoryViewModel( private val preferencesRepository: UserPreferencesRepository, private val repository: SelfServiceRepository, ) : BaseViewModel( @@ -114,7 +114,7 @@ internal class HistoryViewModel( } } -internal data class HistoryState( +data class HistoryState( val clientId: Long, val viewState: ViewState, val transactionType: TransactionType, diff --git a/feature/payments/build.gradle.kts b/feature/payments/build.gradle.kts index ae1b679f8..7bf0fb852 100644 --- a/feature/payments/build.gradle.kts +++ b/feature/payments/build.gradle.kts @@ -8,14 +8,25 @@ * 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.feature.payments" } -dependencies { - implementation(libs.accompanist.pager) +kotlin { + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + + implementation(libs.koin.compose.viewmodel) + implementation(libs.koin.compose) + } + } } \ No newline at end of file diff --git a/feature/payments/consumer-rules.pro b/feature/payments/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/feature/payments/proguard-rules.pro b/feature/payments/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/feature/payments/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/payments/src/main/AndroidManifest.xml b/feature/payments/src/androidMain/AndroidManifest.xml similarity index 100% rename from feature/payments/src/main/AndroidManifest.xml rename to feature/payments/src/androidMain/AndroidManifest.xml diff --git a/feature/payments/src/commonMain/composeResources/drawable/baseline_content_copy.xml b/feature/payments/src/commonMain/composeResources/drawable/baseline_content_copy.xml new file mode 100644 index 000000000..4887f60c1 --- /dev/null +++ b/feature/payments/src/commonMain/composeResources/drawable/baseline_content_copy.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/feature/payments/src/main/res/values/strings.xml b/feature/payments/src/commonMain/composeResources/values/strings.xml similarity index 85% rename from feature/payments/src/main/res/values/strings.xml rename to feature/payments/src/commonMain/composeResources/values/strings.xml index 5eb8935d6..819d3ccf9 100644 --- a/feature/payments/src/main/res/values/strings.xml +++ b/feature/payments/src/commonMain/composeResources/values/strings.xml @@ -9,7 +9,7 @@ See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md --> - Virtual Payment Address (VPA) + Virtual Payment Address (VPA) Mobile Number Receive Show code diff --git a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/PaymentNavigation.kt b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/PaymentNavigation.kt similarity index 100% rename from feature/payments/src/main/kotlin/org/mifospay/feature/payments/PaymentNavigation.kt rename to feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/PaymentNavigation.kt diff --git a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/PaymentsScreen.kt b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/PaymentsScreen.kt similarity index 82% rename from feature/payments/src/main/kotlin/org/mifospay/feature/payments/PaymentsScreen.kt rename to feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/PaymentsScreen.kt index c8ff57c43..a260e3bb4 100644 --- a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/PaymentsScreen.kt +++ b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/PaymentsScreen.kt @@ -11,10 +11,10 @@ package org.mifospay.feature.payments import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.google.accompanist.pager.rememberPagerState +import org.jetbrains.compose.ui.tooling.preview.Preview import org.mifospay.core.ui.MifosScrollableTabRow import org.mifospay.core.ui.utility.TabContent @@ -34,9 +34,12 @@ private fun PaymentScreenContent( tabContents: List, modifier: Modifier = Modifier, ) { - val pagerState = rememberPagerState(initialPage = 0) + val pagerState = rememberPagerState(pageCount = { tabContents.size }) - Column(modifier = modifier.fillMaxSize()) { + Column( + modifier = modifier + .fillMaxSize(), + ) { MifosScrollableTabRow( tabContents = tabContents, pagerState = pagerState, @@ -52,7 +55,7 @@ enum class PaymentsScreenContents { INVOICES, } -@Preview(showBackground = true) +@Preview @Composable private fun PaymentsScreenPreview() { PaymentScreenContent( diff --git a/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/RequestScreen.kt b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/RequestScreen.kt new file mode 100644 index 000000000..1cef5ab50 --- /dev/null +++ b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/RequestScreen.kt @@ -0,0 +1,179 @@ +/* + * 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.payments + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.feature.payments.generated.resources.Res +import mobile_wallet.feature.payments.generated.resources.baseline_content_copy +import mobile_wallet.feature.payments.generated.resources.feature_payments_mobile_number +import mobile_wallet.feature.payments.generated.resources.feature_payments_receive +import mobile_wallet.feature.payments.generated.resources.feature_payments_show_code +import mobile_wallet.feature.payments.generated.resources.feature_payments_vpa +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.resources.vectorResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.core.designsystem.theme.MifosTheme +import org.mifospay.core.ui.utils.EventsEffect + +@Composable +fun RequestScreen( + showQr: (String) -> Unit, + modifier: Modifier = Modifier, + viewModel: TransferViewModel = koinViewModel(), +) { + val clipboard = LocalClipboardManager.current + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + is TransferEvent.OnShowQR -> showQr.invoke(event.externalId) + + is TransferEvent.OnCopyTextToClipboard -> { + clipboard.setText(AnnotatedString(event.text)) + } + } + } + + RequestScreenContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun RequestScreenContent( + state: TransferState, + onAction: (TransferAction) -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + modifier = Modifier.padding(top = 10.dp), + text = stringResource(Res.string.feature_payments_receive), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + ) + + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.Start, + ) { + Text(text = stringResource(Res.string.feature_payments_vpa)) + Text( + text = state.externalId, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } + + FilledTonalIconButton( + onClick = { + onAction(TransferAction.ShowQR(state.externalId)) + }, + ) { + Icon( + imageVector = MifosIcons.QrCode, + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource(Res.string.feature_payments_show_code), + ) + } + } + + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f), + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text(text = stringResource(Res.string.feature_payments_mobile_number)) + Text( + text = state.mobileNo, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } + + FilledTonalIconButton( + onClick = { + onAction(TransferAction.CopyTextToClipboard(state.mobileNo)) + }, + ) { + Icon( + imageVector = vectorResource(Res.drawable.baseline_content_copy), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = "Copy Text", + ) + } + } + } + } +} + +@Preview +@Composable +private fun RequestScreenPreview() { + MifosTheme { + RequestScreenContent( + state = TransferState( + mobileNo = "iisque", + externalId = "nonumes", + ), + onAction = {}, + modifier = Modifier, + ) + } +} diff --git a/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/TransferViewModel.kt b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/TransferViewModel.kt new file mode 100644 index 000000000..18f133b78 --- /dev/null +++ b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/TransferViewModel.kt @@ -0,0 +1,69 @@ +/* + * 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.payments + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.mifospay.core.common.Parcelable +import org.mifospay.core.common.Parcelize +import org.mifospay.core.datastore.UserPreferencesRepository +import org.mifospay.core.ui.utils.BaseViewModel + +private const val KEY = "TransferState" + +class TransferViewModel( + private val repository: UserPreferencesRepository, + savedStateHandle: SavedStateHandle, +) : BaseViewModel( + initialState = savedStateHandle[KEY] ?: run { + val client = requireNotNull(repository.client.value) + + TransferState( + mobileNo = client.mobileNo, + externalId = client.externalId, + ) + }, +) { + init { + stateFlow + .onEach { savedStateHandle[KEY] = it } + .launchIn(viewModelScope) + } + + override fun handleAction(action: TransferAction) { + when (action) { + is TransferAction.ShowQR -> { + sendEvent(TransferEvent.OnShowQR(action.externalId)) + } + + is TransferAction.CopyTextToClipboard -> { + sendEvent(TransferEvent.OnCopyTextToClipboard(action.text)) + } + } + } +} + +@Parcelize +data class TransferState( + val mobileNo: String, + val externalId: String, +) : Parcelable + +sealed interface TransferEvent { + data class OnCopyTextToClipboard(val text: String) : TransferEvent + data class OnShowQR(val externalId: String) : TransferEvent +} + +sealed interface TransferAction { + data class CopyTextToClipboard(val text: String) : TransferAction + data class ShowQR(val externalId: String) : TransferAction +} diff --git a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/di/PaymentsModule.kt b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/di/PaymentsModule.kt similarity index 69% rename from feature/payments/src/main/kotlin/org/mifospay/feature/payments/di/PaymentsModule.kt rename to feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/di/PaymentsModule.kt index 0abf8b38f..fd45ccaf8 100644 --- a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/di/PaymentsModule.kt +++ b/feature/payments/src/commonMain/kotlin/org/mifospay/feature/payments/di/PaymentsModule.kt @@ -9,16 +9,10 @@ */ package org.mifospay.feature.payments.di -import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module import org.mifospay.feature.payments.TransferViewModel val PaymentsModule = module { - viewModel { - TransferViewModel( - mUsecaseHandler = get(), - localRepository = get(), - mFetchAccount = get(), - ) - } + viewModelOf(::TransferViewModel) } diff --git a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt b/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt deleted file mode 100644 index 222a24c3c..000000000 --- a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt +++ /dev/null @@ -1,136 +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.payments - -import androidx.annotation.VisibleForTesting -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import org.koin.androidx.compose.koinViewModel -import org.mifospay.core.designsystem.icon.MifosIcons -import org.mifospay.core.designsystem.theme.MifosTheme - -@Composable -fun RequestScreen( - showQr: (String) -> Unit, - modifier: Modifier = Modifier, - viewModel: TransferViewModel = koinViewModel(), -) { - val vpa by viewModel.vpa.collectAsState() - val mobile by viewModel.mobile.collectAsState() - - RequestScreenContent( - vpa = vpa, - mobile = mobile, - showQr = showQr, - modifier = modifier, - ) -} - -@Composable -@VisibleForTesting -internal fun RequestScreenContent( - vpa: String, - mobile: String, - showQr: (String) -> Unit, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .fillMaxSize(), - ) { - Text( - modifier = Modifier.padding(start = 20.dp, top = 30.dp), - text = stringResource(id = R.string.feature_payments_receive), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary, - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 20.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp) - .weight(1f), - ) { - Column { - Text(text = stringResource(id = R.string.feature_payments_virtual_payment_address_vpa)) - Text( - text = vpa, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - ) - } - - Column(modifier = Modifier.padding(top = 10.dp)) { - Text(text = stringResource(id = R.string.feature_payments_mobile_number)) - Text( - text = mobile, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - IconButton( - onClick = { showQr(vpa) }, - ) { - Icon( - imageVector = MifosIcons.QrCode, - tint = MaterialTheme.colorScheme.primary, - contentDescription = stringResource(id = R.string.feature_payments_show_code), - ) - } - - Text( - text = stringResource(id = R.string.feature_payments_show_code), - ) - } - } - } -} - -@Preview(showBackground = true, showSystemUi = true) -@Composable -private fun RequestScreenPreview() { - MifosTheme { - RequestScreenContent( - vpa = "1234567898", - mobile = "9078563421", - showQr = {}, - modifier = Modifier, - ) - } -} diff --git a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/TransferViewModel.kt b/feature/payments/src/main/kotlin/org/mifospay/feature/payments/TransferViewModel.kt deleted file mode 100644 index 4fb2fb8e0..000000000 --- a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/TransferViewModel.kt +++ /dev/null @@ -1,90 +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.payments - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import org.mifospay.core.data.base.UseCase.UseCaseCallback -import org.mifospay.core.data.base.UseCaseHandler -import org.mifospay.core.data.domain.usecase.account.FetchAccount -import org.mifospay.core.data.repository.local.LocalRepository - -class TransferViewModel( - val mUsecaseHandler: UseCaseHandler, - val localRepository: LocalRepository, - val mFetchAccount: FetchAccount, -) : ViewModel() { - - private val mVpa = MutableStateFlow("") - val vpa: StateFlow = mVpa - - private val mMobile = MutableStateFlow("") - val mobile: StateFlow = mMobile - - private var mTransferUiState = MutableStateFlow(TransferUiState.Loading) - var transferUiState: StateFlow = mTransferUiState - - private var mUpdateSuccess = MutableStateFlow(false) - var updateSuccess: StateFlow = mUpdateSuccess - - init { - fetchVpa() - fetchMobile() - } - - private fun fetchVpa() { - viewModelScope.launch { - mVpa.value = localRepository.clientDetails.externalId.toString() - } - } - - private fun fetchMobile() { - viewModelScope.launch { - mMobile.value = localRepository.preferencesHelper.mobile.toString() - } - } - - fun checkSelfTransfer(externalId: String?): Boolean { - return externalId == localRepository.clientDetails.externalId - } - - fun checkBalanceAvailability(externalId: String, transferAmount: Double) { - mUsecaseHandler.execute( - mFetchAccount, - FetchAccount.RequestValues(localRepository.clientDetails.clientId), - object : UseCaseCallback { - override fun onSuccess(response: FetchAccount.ResponseValue) { - mTransferUiState.value = TransferUiState.Loading - if (transferAmount > response.account.balance) { - mUpdateSuccess.value = true - } else { - mTransferUiState.value = - TransferUiState.ShowClientDetails(externalId, transferAmount) - } - } - - override fun onError(message: String) { - mUpdateSuccess.value = false - } - }, - ) - } -} - -sealed interface TransferUiState { - data object Loading : TransferUiState - data class ShowClientDetails( - val externalId: String, - val transferAmount: Double, - ) : TransferUiState -} diff --git a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt index 042bd93d7..cb013779c 100644 --- a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt +++ b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.tree.txt @@ -2130,6 +2130,37 @@ | | +--- org.jetbrains.compose.components:components-resources:1.7.0-rc01 (*) | | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.7.0-rc01 (*) | | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20 (*) +| +--- project :feature:payments +| | +--- 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 (*) +| | \--- 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 8523f857b..46ee2bf2a 100644 --- a/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt +++ b/mifospay-android/dependencies/prodReleaseRuntimeClasspath.txt @@ -13,6 +13,7 @@ :feature:faq :feature:history :feature:home +:feature:payments :feature:profile :feature:settings :libs:country-code-picker diff --git a/mifospay-shared/build.gradle.kts b/mifospay-shared/build.gradle.kts index d6cb1cc33..033b0f8e1 100644 --- a/mifospay-shared/build.gradle.kts +++ b/mifospay-shared/build.gradle.kts @@ -42,6 +42,7 @@ kotlin { api(projects.feature.editpassword) api(projects.feature.profile) api(projects.feature.history) + api(projects.feature.payments) } 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 a546dc942..de11c05b3 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 @@ -26,6 +26,7 @@ import org.mifospay.feature.editpassword.di.EditPasswordModule import org.mifospay.feature.faq.di.FaqModule import org.mifospay.feature.history.di.HistoryModule import org.mifospay.feature.home.di.HomeModule +import org.mifospay.feature.payments.di.PaymentsModule import org.mifospay.feature.profile.di.ProfileModule import org.mifospay.feature.settings.di.SettingsModule import org.mifospay.shared.MifosPayViewModel @@ -58,6 +59,7 @@ object KoinModules { EditPasswordModule, ProfileModule, HistoryModule, + PaymentsModule, ) } 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 976de9f08..9e4882f9e 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 @@ -12,10 +12,12 @@ package org.mifospay.shared.navigation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost +import org.mifospay.core.ui.utility.TabContent import org.mifospay.feature.editpassword.navigation.editPasswordScreen import org.mifospay.feature.editpassword.navigation.navigateToEditPassword import org.mifospay.feature.faq.navigation.faqScreen import org.mifospay.feature.faq.navigation.navigateToFAQ +import org.mifospay.feature.history.HistoryScreen import org.mifospay.feature.history.navigation.historyNavigation import org.mifospay.feature.history.navigation.navigateToSpecificTransaction import org.mifospay.feature.history.navigation.navigateToTransactionDetail @@ -23,6 +25,9 @@ import org.mifospay.feature.history.navigation.specificTransactionsScreen import org.mifospay.feature.history.navigation.transactionDetailNavigation import org.mifospay.feature.home.navigation.HOME_ROUTE import org.mifospay.feature.home.navigation.homeScreen +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.settings.navigation.settingsScreen import org.mifospay.shared.ui.MifosAppState @@ -35,6 +40,23 @@ internal fun MifosNavHost( ) { val navController = appState.navController + val paymentsTabContents = listOf( + TabContent(PaymentsScreenContents.SEND.name) { + }, + TabContent(PaymentsScreenContents.REQUEST.name) { + RequestScreen(showQr = {}) + }, + TabContent(PaymentsScreenContents.HISTORY.name) { + HistoryScreen( + viewTransferDetail = navController::navigateToTransactionDetail, + ) + }, + TabContent(PaymentsScreenContents.SI.name) { + }, + TabContent(PaymentsScreenContents.INVOICES.name) { + }, + ) + NavHost( route = MifosNavGraph.MAIN_GRAPH, startDestination = HOME_ROUTE, @@ -75,6 +97,10 @@ internal fun MifosNavHost( viewTransactionDetail = navController::navigateToTransactionDetail, ) + paymentsScreen( + tabContents = paymentsTabContents, + ) + specificTransactionsScreen( navigateBack = navController::navigateUp, viewTransactionDetail = navController::navigateToTransactionDetail, diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt index bed6ea94c..4206b894c 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosAppState.kt @@ -31,10 +31,10 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.datetime.TimeZone import org.mifospay.core.data.util.NetworkMonitor import org.mifospay.core.data.util.TimeZoneMonitor -import org.mifospay.feature.history.navigation.HISTORY_ROUTE -import org.mifospay.feature.history.navigation.navigateToHistory import org.mifospay.feature.home.navigation.HOME_ROUTE import org.mifospay.feature.home.navigation.navigateToHome +import org.mifospay.feature.payments.PAYMENTS_ROUTE +import org.mifospay.feature.payments.navigateToPayments import org.mifospay.feature.profile.navigation.PROFILE_ROUTE import org.mifospay.feature.profile.navigation.navigateToProfile import org.mifospay.shared.utils.TopLevelDestination @@ -80,7 +80,7 @@ internal class MifosAppState( val currentTopLevelDestination: TopLevelDestination? @Composable get() = when (currentDestination?.route) { HOME_ROUTE -> TopLevelDestination.HOME - HISTORY_ROUTE -> TopLevelDestination.PAYMENTS + PAYMENTS_ROUTE -> TopLevelDestination.PAYMENTS // FINANCE_ROUTE -> TopLevelDestination.FINANCE PROFILE_ROUTE -> TopLevelDestination.PROFILE else -> null @@ -127,7 +127,7 @@ internal class MifosAppState( when (topLevelDestination) { TopLevelDestination.HOME -> navController.navigateToHome(topLevelNavOptions) - TopLevelDestination.PAYMENTS -> navController.navigateToHistory(topLevelNavOptions) + TopLevelDestination.PAYMENTS -> navController.navigateToPayments(topLevelNavOptions) // TopLevelDestination.FINANCE -> navController.navigateToFinance(topLevelNavOptions) TopLevelDestination.PROFILE -> navController.navigateToProfile(topLevelNavOptions) else -> Unit