mirror of
https://github.com/openMF/mobile-wallet.git
synced 2026-02-06 11:36:57 +00:00
fix: Pay-Money feature module
This commit is contained in:
parent
ba8bda44ca
commit
0b5889bc33
@ -1,24 +0,0 @@
|
||||
package org.mifospay.feature.make.transfer
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("org.mifospay.feature.make.transfer", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@ -44,7 +44,8 @@ import org.mifospay.core.designsystem.component.MifosOverlayLoadingWheel
|
||||
|
||||
@Composable
|
||||
fun MakeTransferScreenRoute(
|
||||
viewModel: MakeTransferViewModel = hiltViewModel()
|
||||
viewModel: MakeTransferViewModel = hiltViewModel(),
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val fetchPayeeClient by viewModel.fetchPayeeClient.collectAsStateWithLifecycle()
|
||||
val makeTransferState by viewModel.makeTransferState.collectAsStateWithLifecycle()
|
||||
@ -58,7 +59,8 @@ fun MakeTransferScreenRoute(
|
||||
toClientId,
|
||||
transferAmount
|
||||
)
|
||||
}
|
||||
},
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@ -67,6 +69,7 @@ fun MakeTransferScreen(
|
||||
uiState: MakeTransferState,
|
||||
showTransactionStatus: ShowTransactionStatus,
|
||||
makeTransfer: (Long, Double) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
when (uiState) {
|
||||
@ -97,7 +100,8 @@ fun MakeTransferScreen(
|
||||
externalId,
|
||||
transferAmount,
|
||||
showTransactionStatus,
|
||||
makeTransfer = makeTransfer
|
||||
makeTransfer = makeTransfer,
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -113,6 +117,7 @@ fun MakeTransferBottomSheetContent(
|
||||
transferAmount: Double,
|
||||
showTransactionStatus: ShowTransactionStatus,
|
||||
makeTransfer: (Long, Double) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
|
||||
@ -125,6 +130,7 @@ fun MakeTransferBottomSheetContent(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
showBottomSheet = false
|
||||
onDismiss.invoke()
|
||||
},
|
||||
dragHandle = { BottomSheetDefaults.DragHandle() },
|
||||
) {
|
||||
@ -340,7 +346,8 @@ fun PreviewWithMakeTransferContentLoading() {
|
||||
showSuccessStatus = false,
|
||||
showErrorStatus = false
|
||||
),
|
||||
makeTransfer = { _, _ -> }
|
||||
makeTransfer = { _, _ -> },
|
||||
onDismiss = { }
|
||||
)
|
||||
}
|
||||
|
||||
@ -359,7 +366,8 @@ fun PreviewWithMakeTransferContentSuccess() {
|
||||
showSuccessStatus = false,
|
||||
showErrorStatus = false
|
||||
),
|
||||
makeTransfer = { _, _ -> }
|
||||
makeTransfer = { _, _ -> },
|
||||
onDismiss = { }
|
||||
)
|
||||
}
|
||||
|
||||
@ -391,7 +399,8 @@ fun PreviewMakeTransferBottomSheetContent() {
|
||||
showSuccessStatus = false,
|
||||
showErrorStatus = false
|
||||
),
|
||||
makeTransfer = { _, _ -> }
|
||||
makeTransfer = { _, _ -> },
|
||||
onDismiss = { }
|
||||
)
|
||||
}
|
||||
|
||||
@ -404,7 +413,8 @@ fun PreviewWithMakeTransferContentError() {
|
||||
showSuccessStatus = false,
|
||||
showErrorStatus = false
|
||||
),
|
||||
makeTransfer = { _, _ -> }
|
||||
makeTransfer = { _, _ -> },
|
||||
onDismiss = { }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,9 @@ fun NavController.navigateToMakeTransferScreen(
|
||||
navigate(route, navOptions)
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.makeTransferScreen() {
|
||||
fun NavGraphBuilder.makeTransferScreen(
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = MAKE_TRANSFER_ROUTE,
|
||||
arguments = listOf(
|
||||
@ -46,6 +48,8 @@ fun NavGraphBuilder.makeTransferScreen() {
|
||||
}
|
||||
)
|
||||
) {
|
||||
MakeTransferScreenRoute()
|
||||
MakeTransferScreenRoute(
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
1
feature/send-money/.gitignore
vendored
Normal file
1
feature/send-money/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
19
feature/send-money/build.gradle.kts
Normal file
19
feature/send-money/build.gradle.kts
Normal file
@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
alias(libs.plugins.mifospay.android.feature)
|
||||
alias(libs.plugins.mifospay.android.library.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.mifospay.feature.send.money"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.data)
|
||||
|
||||
// we need it for country picker library
|
||||
implementation("androidx.compose.material:material:1.6.0")
|
||||
implementation(libs.compose.country.code.picker) // remove after moving auth code to module
|
||||
|
||||
// Google Bar code scanner
|
||||
implementation(libs.google.play.services.code.scanner)
|
||||
}
|
||||
2
feature/send-money/src/main/AndroidManifest.xml
Normal file
2
feature/send-money/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
@ -0,0 +1,97 @@
|
||||
package org.mifospay.feature.send.money
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mifospay.core.data.base.UseCase
|
||||
import org.mifospay.core.data.base.UseCaseHandler
|
||||
import org.mifospay.core.data.domain.usecase.account.FetchAccount
|
||||
import org.mifospay.core.data.repository.local.LocalRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SendPaymentViewModel @Inject constructor(
|
||||
private val useCaseHandler: UseCaseHandler,
|
||||
private val localRepository: LocalRepository,
|
||||
private val fetchAccount: FetchAccount
|
||||
) : ViewModel() {
|
||||
|
||||
private val _showProgress = MutableStateFlow(false)
|
||||
val showProgress: StateFlow<Boolean> = _showProgress
|
||||
|
||||
private val _vpa = MutableStateFlow("")
|
||||
val vpa: StateFlow<String> = _vpa
|
||||
|
||||
private val _mobile = MutableStateFlow("")
|
||||
val mobile: StateFlow<String> = _mobile
|
||||
|
||||
init {
|
||||
fetchVpa()
|
||||
fetchMobile()
|
||||
}
|
||||
|
||||
fun updateProgressState(isVisible: Boolean) {
|
||||
_showProgress.update { isVisible }
|
||||
}
|
||||
|
||||
private fun fetchVpa() {
|
||||
viewModelScope.launch {
|
||||
_vpa.value = localRepository.clientDetails.externalId.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchMobile() {
|
||||
viewModelScope.launch {
|
||||
_mobile.value = localRepository.preferencesHelper.mobile.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun checkSelfTransfer(
|
||||
selfVpa: String?,
|
||||
selfMobile: String?,
|
||||
externalIdOrMobile: String?,
|
||||
sendMethodType: SendMethodType,
|
||||
): Boolean {
|
||||
return when (sendMethodType) {
|
||||
SendMethodType.VPA -> {
|
||||
selfVpa.takeIf { !it.isNullOrEmpty() }?.let { it == externalIdOrMobile } ?: false
|
||||
}
|
||||
|
||||
SendMethodType.MOBILE -> {
|
||||
selfMobile.takeIf { !it.isNullOrEmpty() }?.let { it == externalIdOrMobile } ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkBalanceAvailabilityAndTransfer(
|
||||
externalId: String?,
|
||||
transferAmount: Double,
|
||||
onAnyError: (Int) -> Unit,
|
||||
proceedWithTransferFlow: (String, Double) -> Unit
|
||||
) {
|
||||
updateProgressState(true)
|
||||
useCaseHandler.execute(fetchAccount,
|
||||
FetchAccount.RequestValues(localRepository.clientDetails.clientId),
|
||||
object : UseCase.UseCaseCallback<FetchAccount.ResponseValue> {
|
||||
override fun onSuccess(response: FetchAccount.ResponseValue) {
|
||||
updateProgressState(false)
|
||||
if (transferAmount > response.account.balance) {
|
||||
onAnyError(R.string.insufficient_balance)
|
||||
} else {
|
||||
if (externalId != null) {
|
||||
proceedWithTransferFlow(externalId, transferAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(message: String) {
|
||||
updateProgressState(false)
|
||||
onAnyError.invoke(R.string.error_fetching_balance)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,388 @@
|
||||
package org.mifospay.feature.send.money
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
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.Spacer
|
||||
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.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.QrCode2
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
|
||||
import com.togitech.ccp.component.TogiCountryCodePicker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.mifospay.core.designsystem.component.MfOutlinedTextField
|
||||
import org.mifospay.core.designsystem.component.MfOverlayLoadingWheel
|
||||
import org.mifospay.core.designsystem.component.MifosButton
|
||||
import org.mifospay.core.designsystem.component.MifosNavigationTopAppBar
|
||||
import org.mifospay.core.designsystem.theme.styleMedium16sp
|
||||
import org.mifospay.core.designsystem.theme.styleNormal18sp
|
||||
|
||||
|
||||
enum class SendMethodType {
|
||||
VPA, MOBILE
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SendScreenRoute(
|
||||
viewModel: SendPaymentViewModel = hiltViewModel(),
|
||||
showToolBar: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
proceedWithMakeTransferFlow: (String, String) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val selfVpa by viewModel.vpa.collectAsStateWithLifecycle()
|
||||
val selfMobile by viewModel.mobile.collectAsStateWithLifecycle()
|
||||
val showProgress by viewModel.showProgress.collectAsStateWithLifecycle()
|
||||
|
||||
fun showToast(message: String) {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
SendMoneyScreen(
|
||||
showToolBar = showToolBar,
|
||||
onBackClick = onBackClick,
|
||||
showProgress = showProgress,
|
||||
onSubmit = { amount, externalIdOrMobile, sendMethodType ->
|
||||
if (!viewModel.checkSelfTransfer(
|
||||
selfVpa = selfVpa,
|
||||
selfMobile = selfMobile,
|
||||
sendMethodType = sendMethodType,
|
||||
externalIdOrMobile = externalIdOrMobile
|
||||
)
|
||||
) {
|
||||
viewModel.checkBalanceAvailabilityAndTransfer(
|
||||
externalId = selfVpa,
|
||||
transferAmount = amount.toDouble(),
|
||||
onAnyError = {
|
||||
showToast(context.getString(it))
|
||||
},
|
||||
proceedWithTransferFlow = { externalId, transferAmount ->
|
||||
proceedWithMakeTransferFlow.invoke(externalIdOrMobile, transferAmount.toString())
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showToast(context.getString(R.string.self_amount_transfer_is_not_allowed))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SendMoneyScreen(
|
||||
showToolBar: Boolean,
|
||||
showProgress: Boolean,
|
||||
onSubmit: (String, String, SendMethodType) -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
var amount by rememberSaveable { mutableStateOf("") }
|
||||
var vpa by rememberSaveable { mutableStateOf("") }
|
||||
var mobileNumber by rememberSaveable { mutableStateOf("") }
|
||||
var isValidMobileNumber by rememberSaveable { mutableStateOf(false) }
|
||||
var sendMethodType by rememberSaveable { mutableStateOf(SendMethodType.VPA) }
|
||||
var isValidInfo by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var contactUri by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||
|
||||
fun validateInfo() {
|
||||
isValidInfo = when (sendMethodType) {
|
||||
SendMethodType.VPA -> amount.isNotEmpty() && vpa.isNotEmpty()
|
||||
SendMethodType.MOBILE -> {
|
||||
isValidMobileNumber && mobileNumber.isNotEmpty() && amount.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val contactLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.PickContact()
|
||||
) { uri: Uri? ->
|
||||
uri?.let { contactUri = uri }
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = contactUri) {
|
||||
contactUri?.let {
|
||||
mobileNumber = getContactPhoneNumber(it, context)
|
||||
}
|
||||
}
|
||||
|
||||
val permissionLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = { isGranted: Boolean ->
|
||||
if (isGranted) {
|
||||
contactLauncher.launch(null)
|
||||
} else {
|
||||
// Handle permission denial
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val options = GmsBarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(
|
||||
Barcode.FORMAT_QR_CODE,
|
||||
Barcode.FORMAT_AZTEC
|
||||
)
|
||||
.build()
|
||||
|
||||
val scanner = GmsBarcodeScanning.getClient(context, options)
|
||||
|
||||
fun startScan() {
|
||||
scanner.startScan()
|
||||
.addOnSuccessListener { barcode ->
|
||||
barcode.rawValue?.let {
|
||||
vpa = it
|
||||
}
|
||||
}
|
||||
.addOnCanceledListener {
|
||||
// Task canceled
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
// Task failed with an exception
|
||||
e.localizedMessage?.let { Log.d("SendMoney: Barcode scan failed", it) }
|
||||
}
|
||||
}
|
||||
|
||||
Box {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
if (showToolBar) {
|
||||
MifosNavigationTopAppBar(
|
||||
titleRes = R.string.send,
|
||||
onNavigationClick = onBackClick
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 20.dp, top = 20.dp),
|
||||
text = stringResource(id = R.string.select_transfer_method),
|
||||
style = styleNormal18sp
|
||||
)
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 20.dp, bottom = 20.dp)
|
||||
) {
|
||||
VpaMobileChip(
|
||||
selected = sendMethodType == SendMethodType.VPA,
|
||||
onClick = { sendMethodType = SendMethodType.VPA },
|
||||
label = stringResource(id = R.string.vpa)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
VpaMobileChip(
|
||||
selected = sendMethodType == SendMethodType.MOBILE,
|
||||
onClick = { sendMethodType = SendMethodType.MOBILE },
|
||||
label = stringResource(id = R.string.mobile)
|
||||
)
|
||||
}
|
||||
MfOutlinedTextField(
|
||||
value = amount,
|
||||
onValueChange = {
|
||||
amount = it
|
||||
validateInfo()
|
||||
},
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
|
||||
label = stringResource(id = R.string.amount),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
when (sendMethodType) {
|
||||
SendMethodType.VPA -> {
|
||||
MfOutlinedTextField(
|
||||
value = vpa,
|
||||
onValueChange = {
|
||||
vpa = it
|
||||
validateInfo()
|
||||
},
|
||||
label = stringResource(id = R.string.virtual_payment_address),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = {
|
||||
startScan()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.QrCode2,
|
||||
contentDescription = "Scan QR",
|
||||
tint = Color.Blue
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SendMethodType.MOBILE -> {
|
||||
EnterPhoneScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
initialPhoneNumber = mobileNumber,
|
||||
onNumberUpdated = { _, fullPhone, valid ->
|
||||
if (valid) {
|
||||
mobileNumber = fullPhone
|
||||
}
|
||||
isValidMobileNumber = valid
|
||||
validateInfo()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
MifosButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
color = Color.Black,
|
||||
enabled = isValidInfo,
|
||||
onClick = {
|
||||
if (!isValidInfo) return@MifosButton
|
||||
onSubmit(
|
||||
amount,
|
||||
when (sendMethodType) {
|
||||
SendMethodType.VPA -> vpa
|
||||
SendMethodType.MOBILE -> mobileNumber
|
||||
},
|
||||
sendMethodType
|
||||
)
|
||||
//TODO: Navigate to MakeTransferScreenRoute
|
||||
},
|
||||
contentPadding = PaddingValues(12.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = R.string.submit),
|
||||
style = styleMedium16sp.copy(color = Color.White)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showProgress) {
|
||||
MfOverlayLoadingWheel(
|
||||
contentDesc = stringResource(id = R.string.please_wait)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EnterPhoneScreen(
|
||||
modifier: Modifier,
|
||||
initialPhoneNumber: String? = null,
|
||||
onNumberUpdated: (String, String, Boolean) -> Unit
|
||||
) {
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
TogiCountryCodePicker(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
initialPhoneNumber = initialPhoneNumber,
|
||||
onValueChange = { (code, phone), isValid ->
|
||||
onNumberUpdated(phone, code + phone, isValid)
|
||||
},
|
||||
label = { Text(stringResource(id = R.string.phone_number)) },
|
||||
keyboardActions = KeyboardActions { keyboardController?.hide() }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VpaMobileChip(selected: Boolean, onClick: () -> Unit, label: String) {
|
||||
MifosButton(
|
||||
onClick = onClick,
|
||||
color = if (selected) Color.Black else Color.LightGray,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.wrapContentSize()
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp, bottom = 4.dp),
|
||||
text = label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getContactPhoneNumber(uri: Uri, context: Context): String {
|
||||
val contactId: String = uri.lastPathSegment ?: return ""
|
||||
return withContext(Dispatchers.IO) {
|
||||
val phoneCursor = context.contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
|
||||
"${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?",
|
||||
arrayOf(contactId),
|
||||
null
|
||||
)
|
||||
phoneCursor?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val phoneNumberIndex =
|
||||
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
|
||||
cursor.getString(phoneNumberIndex)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(showSystemUi = true, showBackground = true)
|
||||
@Composable
|
||||
fun SendMoneyScreenWithToolBarPreview() {
|
||||
SendMoneyScreen(
|
||||
onSubmit = { _, _, _ -> },
|
||||
onBackClick = {},
|
||||
showProgress = false,
|
||||
showToolBar = true
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true, showBackground = true)
|
||||
@Composable
|
||||
fun SendMoneyScreenWithoutToolBarPreview() {
|
||||
SendMoneyScreen(
|
||||
onSubmit = { _, _, _ -> },
|
||||
onBackClick = {},
|
||||
showProgress = false,
|
||||
showToolBar = false
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.mifospay.feature.send.money.navigation
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import org.mifospay.feature.send.money.SendScreenRoute
|
||||
|
||||
const val SEND_MONEY_ROUTE = "send_money_route"
|
||||
|
||||
fun NavController.navigateToSendMoneyScreen(
|
||||
navOptions: NavOptions? = null
|
||||
) = navigate(SEND_MONEY_ROUTE, navOptions)
|
||||
|
||||
fun NavGraphBuilder.sendMoneyScreen(
|
||||
proceedWithMakeTransferFlow: (String, String?) -> Unit,
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
composable(route = SEND_MONEY_ROUTE) {
|
||||
SendScreenRoute(
|
||||
showToolBar = true,
|
||||
onBackClick = onBackClick,
|
||||
proceedWithMakeTransferFlow = proceedWithMakeTransferFlow
|
||||
)
|
||||
}
|
||||
}
|
||||
14
feature/send-money/src/main/res/values/strings.xml
Normal file
14
feature/send-money/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<resources>
|
||||
<string name="insufficient_balance">Insufficient balance</string>
|
||||
<string name="error_fetching_balance">Error fetching balance</string>
|
||||
<string name="self_amount_transfer_is_not_allowed">Self Account transfer is not allowed</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="select_transfer_method">Select transfer method</string>
|
||||
<string name="vpa">VPA</string>
|
||||
<string name="mobile">Mobile number</string>
|
||||
<string name="amount">Amount</string>
|
||||
<string name="virtual_payment_address">Virtual Payment Address</string>
|
||||
<string name="submit">Submit</string>
|
||||
<string name="please_wait">Please wait…</string>
|
||||
<string name="phone_number">Phone Number</string>
|
||||
</resources>
|
||||
@ -0,0 +1,17 @@
|
||||
package org.mifospay.feature.send.money
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,7 @@ import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by naman on 30/8/17.
|
||||
* Moved to the feature/make-transfer
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MakeTransferFragment : BottomSheetDialogFragment(), TransferContract.TransferView {
|
||||
|
||||
@ -74,7 +74,9 @@ fun MifosNavHost(
|
||||
navController.navigateToMakeTransferScreen(externalId, transferAmount)
|
||||
}
|
||||
)
|
||||
makeTransferScreen()
|
||||
makeTransferScreen(
|
||||
onDismiss = navController::popBackStack
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,3 +36,4 @@ include(":core:analytics")
|
||||
include(":feature:passcode")
|
||||
include(":feature:auth")
|
||||
include(":feature:make-transfer")
|
||||
include(":feature:send-money")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user