diff --git a/core/ui/src/main/kotlin/org/mifospay/core/ui/ExpiryDateInput.kt b/core/ui/src/main/kotlin/org/mifospay/core/ui/ExpiryDateInput.kt new file mode 100644 index 000000000..0a53dfbf1 --- /dev/null +++ b/core/ui/src/main/kotlin/org/mifospay/core/ui/ExpiryDateInput.kt @@ -0,0 +1,116 @@ +package org.mifos.mobilewallet.mifospay.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun ExpiryDateInput( + date: String, + onDateChange: (String) -> Unit, + onDone: () -> Unit, +) { + val (a, b, c) = FocusRequester.createRefs() + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), verticalAlignment = Alignment.Bottom + ) { + BasicTextField(modifier = Modifier + .focusRequester(b) + .focusProperties { + next = c + }, + value = TextFieldValue(date, selection = TextRange(date.length)), + onValueChange = { + if (it.text.length == 3 && it.text[2] != '/') { + val newText = it.text.substring(0, 2) + '/' + it.text.substring(2) + onDateChange(newText) + return@BasicTextField + } + if (it.text.length > date.length) { + // If the user is typing at the third position, ensure it remains '/' + if (it.text.length >= 3 && it.text[2] != '/') { + val newText = it.text.substring(0, 2) + '/' + it.text.substring(3) + onDateChange(newText) + return@BasicTextField + } + } + onDateChange(it.text) + }, + keyboardActions = KeyboardActions(onDone = { + onDone() + }), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), + decorationBox = { + Row(horizontalArrangement = Arrangement.Center) { + repeat(7) { index -> + FormattedDateView( + index = index, text = date + ) + Spacer(modifier = Modifier.width(8.dp)) + } + } + }) + } +} + +@Composable +fun FormattedDateView( + index: Int, text: String +) { + val isFocused = text.length == index + + val char = when { + index == 2 -> "/" + index == text.length -> "_" + index > text.length -> "_" + else -> text[index].toString() + } + androidx.compose.material3.Text( + modifier = Modifier + .width(40.dp) + .wrapContentHeight(align = Alignment.CenterVertically), + text = char, + style = MaterialTheme.typography.headlineSmall, + color = if (isFocused) { + Color.DarkGray + } else { + Color.LightGray + }, + textAlign = TextAlign.Center + ) +} + +@Preview(showBackground = true) +@Composable +fun ExpiryDateInputPreview() { + ExpiryDateInput(date = "", onDateChange = {}, onDone = {}) +} + diff --git a/core/ui/src/main/kotlin/org/mifospay/core/ui/HeadingTitile.kt b/core/ui/src/main/kotlin/org/mifospay/core/ui/HeadingTitile.kt new file mode 100644 index 000000000..536b26946 --- /dev/null +++ b/core/ui/src/main/kotlin/org/mifospay/core/ui/HeadingTitile.kt @@ -0,0 +1,66 @@ +package org.mifos.mobilewallet.mifospay.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.mifospay.core.designsystem.theme.MifosTheme + + +@Composable +fun VerifyStepHeader(text: String, isVerified: Boolean) { + Row( + modifier = Modifier + .fillMaxWidth(), + ) { + Text( + text = text, + color = Color(0xFF212121), + fontSize = 17.sp, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .padding(16.dp) + .weight(1f), + textAlign = TextAlign.Start, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + IconButton( + onClick = { }, + ) { + if (isVerified) + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = if (isVerified) Color.Black else Color.Gray, + modifier = Modifier.size(24.dp) + ) + } + } +} +@Preview +@Composable +fun VerifyStepHeaderVerifiedPreview() { + MifosTheme { + VerifyStepHeader(text = "Debit card Details ", isVerified = true) + } +} + +@Preview +@Composable +fun VerifyStepHeaderUnverifiedPreview() { + VerifyStepHeader(text = "Enter OTP ", isVerified = false) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/mifospay/core/ui/OtpTextField.kt b/core/ui/src/main/kotlin/org/mifospay/core/ui/OtpTextField.kt new file mode 100644 index 000000000..f7291ce05 --- /dev/null +++ b/core/ui/src/main/kotlin/org/mifospay/core/ui/OtpTextField.kt @@ -0,0 +1,125 @@ +package org.mifos.mobilewallet.mifospay.ui + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun OtpTextField( + modifier: Modifier = Modifier, + realOtp: String = "", + otpCount: Int = 4, + onOtpTextCorrectlyEntered: () -> Unit +) { + var otpText by remember { mutableStateOf("") } + var isError by remember { mutableStateOf(false) } + Column( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + BasicTextField(modifier = modifier, + value = TextFieldValue(otpText, selection = TextRange(otpText.length)), + onValueChange = { + otpText = it.text + isError = false + if (otpText.length == otpCount) { + if (otpText != realOtp) { + isError = true + } else { + onOtpTextCorrectlyEntered.invoke() + } + } + }, + keyboardActions = KeyboardActions(onDone = { + if (otpText != realOtp) { + isError = true + } else { + onOtpTextCorrectlyEntered.invoke() + } + println("OTP: $otpText and $isError") + }), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), + decorationBox = { + Row(horizontalArrangement = Arrangement.Center) { + repeat(otpCount) { index -> + CharView( + index = index, text = otpText + ) + Spacer(modifier = Modifier.width(8.dp)) + } + } + }) + if (isError) { + // display erro message in text + Text( + text = "Invalid OTP", + style = MaterialTheme.typography.bodyMedium, + color = Color.Red, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 8.dp) + ) + } + } +} + +@Composable +fun CharView( + index: Int, text: String +) { + val isFocused = text.length == index + val char = when { + index == text.length -> "_" + index > text.length -> "_" + else -> text[index].toString() + } + Text( + modifier = Modifier + .width(40.dp) + .wrapContentHeight(align = Alignment.CenterVertically), + text = char, + style = MaterialTheme.typography.headlineSmall, + color = if (isFocused) { + Color.DarkGray + } else { + Color.LightGray + }, + textAlign = TextAlign.Center + ) +} + +@Preview +@Composable +fun prviewOtpTextField() { + OtpTextField( + realOtp = "1234", + otpCount = 4, + onOtpTextCorrectlyEntered = {} + ) +} diff --git a/mifospay/src/main/java/org/mifospay/bank/fragment/DebitCardFragment.kt b/mifospay/src/main/java/org/mifospay/bank/fragment/DebitCardFragment.kt deleted file mode 100644 index 162c653e8..000000000 --- a/mifospay/src/main/java/org/mifospay/bank/fragment/DebitCardFragment.kt +++ /dev/null @@ -1,123 +0,0 @@ -package org.mifospay.bank.fragment - -import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.EditText -import android.widget.TextView.OnEditorActionListener -import butterknife.BindView -import butterknife.ButterKnife -import com.alimuzaffar.lib.pin.PinEntryEditText -import dagger.hilt.android.AndroidEntryPoint -import org.mifospay.R -import org.mifospay.bank.BankContract -import org.mifospay.bank.BankContract.DebitCardView -import org.mifospay.bank.presenter.DebitCardPresenter -import org.mifospay.bank.ui.SetupUpiPinActivity -import org.mifospay.base.BaseFragment -import org.mifospay.common.Constants -import org.mifospay.utils.Toaster -import javax.inject.Inject - -/** - * Created by ankur on 13/July/2018 - */ -@AndroidEntryPoint -class DebitCardFragment : BaseFragment(), DebitCardView { - @JvmField - @Inject - var mPresenter: DebitCardPresenter? = null - var mDebitCardPresenter: BankContract.DebitCardPresenter? = null - - @JvmField - @BindView(R.id.et_debit_card_number) - var mEtDebitCardNumber: EditText? = null - - @JvmField - @BindView(R.id.pe_month) - var mPeMonth: PinEntryEditText? = null - - @JvmField - @BindView(R.id.pe_year) - var mPeYear: PinEntryEditText? = null - var key = false - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val rootView = inflater.inflate( - R.layout.fragment_debit_card, - container, false - ) as ViewGroup - ButterKnife.bind(this, rootView) - mPresenter!!.attachView(this) - mPeYear!!.setOnEditorActionListener(OnEditorActionListener { v, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - okayClicked() - return@OnEditorActionListener true - } - false - }) - mPeMonth!!.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - if (s.length == 2) { - mPeYear!!.requestFocus() - } - } - - override fun afterTextChanged(s: Editable) {} - }) - mPeYear!!.setOnKeyListener(View.OnKeyListener { v, keyCode, event -> - if (keyCode == KeyEvent.KEYCODE_DEL && mPeYear!!.length() == 0) { - if (key) { - mPeMonth!!.requestFocus() - mPeMonth!!.dispatchKeyEvent( - KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL) - ) - key = false - return@OnKeyListener false - } - key = true - } - false - }) - return rootView - } - - private fun okayClicked() { - showProgressDialog(Constants.PLEASE_WAIT) - mDebitCardPresenter!!.verifyDebitCard(mEtDebitCardNumber!!.text - .toString().trim { it <= ' ' }, - mPeMonth!!.text.toString(), - mPeYear!!.text.toString() - ) - } - - override fun verifyDebitCardSuccess(otp: String?) { - hideProgressDialog() - if (activity is SetupUpiPinActivity) { - (activity as SetupUpiPinActivity?)!!.debitCardVerified(otp) - } - } - - override fun verifyDebitCardError(message: String?) { - hideProgressDialog() - mEtDebitCardNumber!!.requestFocusFromTouch() - showToast(message) - } - - override fun setPresenter(presenter: BankContract.DebitCardPresenter?) { - mDebitCardPresenter = presenter - } - - fun showToast(message: String?) { - Toaster.showToast(activity, message) - } -} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/fragment/OtpFragment.kt b/mifospay/src/main/java/org/mifospay/bank/fragment/OtpFragment.kt deleted file mode 100644 index 26364700d..000000000 --- a/mifospay/src/main/java/org/mifospay/bank/fragment/OtpFragment.kt +++ /dev/null @@ -1,81 +0,0 @@ -package org.mifospay.bank.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.TextView -import android.widget.TextView.OnEditorActionListener -import butterknife.BindView -import butterknife.ButterKnife -import com.alimuzaffar.lib.pin.PinEntryEditText -import dagger.hilt.android.AndroidEntryPoint -import org.mifospay.R -import org.mifospay.bank.ui.SetupUpiPinActivity -import org.mifospay.base.BaseFragment -import org.mifospay.common.Constants -import org.mifospay.utils.Toaster - -/** - * Created by ankur on 13/July/2018 - */ -@AndroidEntryPoint -class OtpFragment : BaseFragment() { - @JvmField - @BindView(R.id.tv_title) - var mTvTitle: TextView? = null - - @JvmField - @BindView(R.id.pe_otp) - var mPeOtp: PinEntryEditText? = null - private var otp: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (arguments != null) { - otp = requireArguments().getString(Constants.OTP) - } - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val rootView = inflater.inflate(R.layout.fragment_otp, container, false) as ViewGroup - ButterKnife.bind(this, rootView) - mPeOtp!!.setOnEditorActionListener(OnEditorActionListener { v, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - okayClicked() - return@OnEditorActionListener true - } - false - }) - mPeOtp!!.requestFocus() - return rootView - } - - fun okayClicked() { - if (activity is SetupUpiPinActivity) { - if (mPeOtp!!.text.toString() == otp) { - (activity as SetupUpiPinActivity?)!!.otpVerified() - } else { - showToast(getString(R.string.wrong_otp)) - } - } - } - - fun showToast(message: String?) { - Toaster.showToast(activity, message) - } - - companion object { - fun newInstance(otp: String?): OtpFragment { - val args = Bundle() - args.putString(Constants.OTP, otp) - val fragment = OtpFragment() - fragment.arguments = args - return fragment - } - } -} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/fragment/SetupUpiPinDialog.kt b/mifospay/src/main/java/org/mifospay/bank/fragment/SetupUpiPinDialog.kt deleted file mode 100644 index c2d9b4ee6..000000000 --- a/mifospay/src/main/java/org/mifospay/bank/fragment/SetupUpiPinDialog.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.mifospay.bank.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.viewpager.widget.ViewPager -import butterknife.BindView -import butterknife.ButterKnife -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import dagger.hilt.android.AndroidEntryPoint -import org.mifospay.R -import org.mifospay.bank.adapters.UpiPinPagerAdapter -import org.mifospay.common.Constants - -/** - * Created by ankur on 16/July/2018 - */ -@AndroidEntryPoint -class SetupUpiPinDialog : BottomSheetDialogFragment() { - @JvmField - @BindView(R.id.vp_setup_upi_pin) - var mVpSetupUpiPin: ViewPager? = null - private var mBottomSheetBehavior: BottomSheetBehavior<*>? = null - private val type: String? = null - private lateinit var mSetupUpiPinFragments: Array - private var upiPinPagerAdapter: UpiPinPagerAdapter? = null - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val rootView = View.inflate(context, R.layout.dialog_setup_upi_pin2, null) - mBottomSheetBehavior = BottomSheetBehavior.from(rootView.parent as View) - ButterKnife.bind(this, rootView) - mSetupUpiPinFragments = arrayOfNulls(4) - mSetupUpiPinFragments[0] = DebitCardFragment() - mSetupUpiPinFragments[1] = OtpFragment() - mSetupUpiPinFragments[2] = UpiPinFragment() - mSetupUpiPinFragments[3] = UpiPinFragment() - upiPinPagerAdapter = UpiPinPagerAdapter( - childFragmentManager, - mSetupUpiPinFragments - ) - return rootView - } - - override fun onStart() { - super.onStart() - mBottomSheetBehavior!!.state = BottomSheetBehavior.STATE_EXPANDED - } - - companion object { - fun newInstance(type: String?): SetupUpiPinDialog { - val args = Bundle() - args.putString(Constants.TYPE, type) - val fragment = SetupUpiPinDialog() - fragment.arguments = args - return fragment - } - } -} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/fragment/UpiPinFragment.kt b/mifospay/src/main/java/org/mifospay/bank/fragment/UpiPinFragment.kt deleted file mode 100644 index 7092aa1b8..000000000 --- a/mifospay/src/main/java/org/mifospay/bank/fragment/UpiPinFragment.kt +++ /dev/null @@ -1,108 +0,0 @@ -package org.mifospay.bank.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.TextView -import android.widget.TextView.OnEditorActionListener -import butterknife.BindView -import butterknife.ButterKnife -import com.alimuzaffar.lib.pin.PinEntryEditText -import dagger.hilt.android.AndroidEntryPoint -import org.mifospay.R -import org.mifospay.bank.BankContract -import org.mifospay.bank.BankContract.UpiPinView -import org.mifospay.bank.presenter.UpiPinPresenter -import org.mifospay.bank.ui.SetupUpiPinActivity -import org.mifospay.base.BaseFragment -import org.mifospay.common.Constants -import org.mifospay.utils.Toaster -import javax.inject.Inject - -/** - * Created by ankur on 13/July/2018 - */ -@AndroidEntryPoint -class UpiPinFragment : BaseFragment(), UpiPinView { - @JvmField - @Inject - var mPresenter: UpiPinPresenter? = null - var mUpiPinPresenter: BankContract.UpiPinPresenter? = null - - @JvmField - @BindView(R.id.tv_title) - var mTvTitle: TextView? = null - - @JvmField - @BindView(R.id.pe_upi_pin) - var mPeUpiPin: PinEntryEditText? = null - private var step = 0 - private var upiPin: String? = null - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val rootView = inflater.inflate( - R.layout.fragment_upi_pin_setup, - container, false - ) as ViewGroup - ButterKnife.bind(this, rootView) - mPresenter!!.attachView(this) - val b = arguments - if (b != null) { - step = b.getInt(Constants.STEP, 0) - upiPin = b.getString(Constants.UPI_PIN, null) - mTvTitle!!.setText(R.string.reenter_upi) - } - mPeUpiPin!!.setOnEditorActionListener(OnEditorActionListener { v, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - okayClicked() - return@OnEditorActionListener true - } - false - }) - mPeUpiPin!!.requestFocus() - return rootView - } - - fun okayClicked() { - if (activity is SetupUpiPinActivity) { - if (mPeUpiPin!!.text.toString().length == 4) { - if (step == 1) { - if (upiPin == mPeUpiPin!!.text.toString()) { - (activity as SetupUpiPinActivity?)!!.upiPinConfirmed(upiPin) - } else { - showToast(getString(R.string.upi_pin_mismatch)) - } - } else { - (activity as SetupUpiPinActivity?)!!.upiPinEntered( - mPeUpiPin!!.text.toString() - ) - } - } else { - showToast(getString(R.string.enter_upi_length_4)) - } - } - } - - fun showToast(message: String?) { - Toaster.showToast(activity, message) - } - - companion object { - fun newInstance(step: Int, upiPin: String?): UpiPinFragment { - val args = Bundle() - args.putInt(Constants.STEP, step) - args.putString(Constants.UPI_PIN, upiPin) - val fragment = UpiPinFragment() - fragment.arguments = args - return fragment - } - } - - override fun setPresenter(presenter: BankContract.UpiPinPresenter?) { - mUpiPinPresenter = presenter - } -} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/DebitCardScreen.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/DebitCardScreen.kt new file mode 100644 index 000000000..a583b2393 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/DebitCardScreen.kt @@ -0,0 +1,134 @@ +package org.mifos.mobilewallet.mifospay.seup_upi.fragment + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +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 org.mifos.mobilewallet.mifospay.seup_upi.presenter.DebitCardUiState +import org.mifos.mobilewallet.mifospay.seup_upi.presenter.DebitCardViewModel +import org.mifospay.bank.setupUpi.screens.DebitCardScreenContents +import org.mifos.mobilewallet.mifospay.ui.VerifyStepHeader +import org.mifospay.core.designsystem.component.MifosLoadingWheel +import org.mifospay.core.designsystem.theme.MifosTheme +import org.mifospay.R + +@Composable +fun DebitCardScreen( + viewModel: DebitCardViewModel = hiltViewModel(), + verificationStatus: Boolean = false, + isContentVisible: Boolean = true, + onDebitCardVerified: (String) -> Unit, + onDebitCardVerificationFailed: (String) -> Unit +) { + val debitCardUiState by viewModel.debitCardUiState.collectAsStateWithLifecycle() + DebitCardScreenWithHeaderAndContent( + debitCardUiState = debitCardUiState, + verificationStatus = verificationStatus, + isContentVisible = isContentVisible, + onDebitCardVerified = onDebitCardVerified, + onDebitCardVerificationFailed = onDebitCardVerificationFailed + ) +} + +@Composable +fun DebitCardScreenWithHeaderAndContent( + debitCardUiState: DebitCardUiState = DebitCardUiState.Initials, + verificationStatus: Boolean = false, + isContentVisible: Boolean = true, + onDebitCardVerified: (String) -> Unit, + onDebitCardVerificationFailed: (String) -> Unit +) { + + Card( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(id = R.dimen.value_20dp)), + elevation = 1.dp + ) { + Column( + modifier = Modifier.padding( + top = dimensionResource(id = R.dimen.value_15dp), + bottom = dimensionResource(id = R.dimen.value_15dp), + start = dimensionResource(id = R.dimen.value_10dp), + end = dimensionResource(id = R.dimen.value_10dp) + ) + ) { + VerifyStepHeader("Debit Card Details ", verificationStatus) + if (isContentVisible) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp) + ) { + //handle debit card ui state + Box( + modifier = Modifier.fillMaxWidth() + ) { + DebitCardScreenContents() + when (debitCardUiState) { + is DebitCardUiState.Initials -> { + + } + is DebitCardUiState.Verifying -> { + MifosLoadingWheel( + modifier = Modifier + .wrapContentSize() + .align(Alignment.Center), + contentDesc = "Verifying Debit Card" + ) + } + is DebitCardUiState.Verified -> { + onDebitCardVerified((debitCardUiState).otp) + } + is DebitCardUiState.VerificationFailed -> { + onDebitCardVerificationFailed((debitCardUiState).errorMessage) + } + else -> {} + } + } + } + } + } + } +} + +@Preview +@Composable +fun DebitCardScreenInitialsPreview() { + MifosTheme { + DebitCardScreenWithHeaderAndContent(debitCardUiState = DebitCardUiState.Initials, + onDebitCardVerified = {}, + onDebitCardVerificationFailed = {}) + } +} + +@Preview +@Composable +fun DebitCardScreenLoadingPreview() { + MifosTheme { + DebitCardScreenWithHeaderAndContent(debitCardUiState = DebitCardUiState.Verifying, + onDebitCardVerified = {}, + onDebitCardVerificationFailed = {}) + } +} + +@Preview +@Composable +fun DebitCardScreenVerificationFailedPreview() { + MifosTheme { + DebitCardScreenWithHeaderAndContent(debitCardUiState = DebitCardUiState.VerificationFailed("Error Message"), + onDebitCardVerified = {}, + onDebitCardVerificationFailed = {}) + } +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/DebitCardScreenContent.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/DebitCardScreenContent.kt new file mode 100644 index 000000000..c0d9271b6 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/DebitCardScreenContent.kt @@ -0,0 +1,133 @@ +package org.mifospay.bank.setupUpi.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +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.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import org.mifos.mobilewallet.mifospay.seup_upi.presenter.DebitCardViewModel +import org.mifos.mobilewallet.mifospay.ui.ExpiryDateInput + + +@Composable +@OptIn(ExperimentalComposeUiApi::class) +fun DebitCardScreenContents( + viewModel: DebitCardViewModel = hiltViewModel() +) { + var cardNumber by rememberSaveable { mutableStateOf("") } + var expiryDate by rememberSaveable { mutableStateOf("") } + val focusManager = LocalFocusManager.current + Column { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth(), + label = { + Text(text = "Debit Card Number", + style = TextStyle(color = Color.Black) + ) + }, + value = cardNumber, + onValueChange = { + cardNumber = it + }, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { + focusManager.moveFocus(FocusDirection.Down) + }), + visualTransformation = VisualTransformation { + val formattedCardNumber = formatCardNumber(it) + formattedCardNumber + }, + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = Color.DarkGray, + unfocusedBorderColor = Color.LightGray, + cursorColor = Color.Black + ) + ) + Spacer(modifier = Modifier.height(8.dp)) + ExpiryDateInput( + date = expiryDate, + onDateChange = { expiryDate = it }, + onDone = { + viewModel.verifyDebitCard(cardNumber, "month, year", expiryDate) + } + ) + } +} + +fun formatCardNumber(text: AnnotatedString): TransformedText { + val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text + var out = "" + for (i in trimmed.indices) { + out += trimmed[i] + if (i % 4 == 3 && i != 15) { + out += "-" + } + } + val creditCardOffsetTranslator = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (offset <= 3) { + return offset + } + if (offset <= 7) { + return offset + 1 + } + if (offset <= 11) { + return offset + 2 + } + if (offset <= 16) { + return offset + 3 + } + return 19 + } + + override fun transformedToOriginal(offset: Int): Int { + if (offset <= 4) { + return offset + } + if (offset <= 9) { + return offset - 1 + } + if (offset <= 14) { + return offset - 2 + } + if (offset <= 19) { + return offset - 3 + } + return 16 + } + } + return TransformedText(AnnotatedString(out), creditCardOffsetTranslator) +} + +@Preview +@Composable +fun DebitCardScreenContentsPreview() { + DebitCardScreenContents() +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/OtpScreen.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/OtpScreen.kt new file mode 100644 index 000000000..a1f8da401 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/OtpScreen.kt @@ -0,0 +1,90 @@ +package org.mifos.mobilewallet.mifospay.seup_upi.fragment + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Card +import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +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.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.mifos.mobilewallet.mifospay.ui.VerifyStepHeader +import org.mifospay.core.designsystem.theme.MifosTheme +import org.mifospay.R +import org.mifos.mobilewallet.mifospay.ui.OtpTextField + + +@Composable +fun OtpScreen( + verificationStatus: Boolean = false, + contentVisibility: Boolean = false, + realOtp: String, + onOtpTextCorrectlyEntered: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding( + top = dimensionResource(id = R.dimen.value_15dp), + bottom = dimensionResource(id = R.dimen.value_15dp), + start = dimensionResource(id = R.dimen.value_10dp), + end = dimensionResource(id = R.dimen.value_10dp) + ), elevation = 1.dp + ) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + VerifyStepHeader("OtpSetUp", verificationStatus) + if (contentVisibility) { + OtpScreenContent(realOtp, onOtpTextCorrectlyEntered) + } + } + } +} + +@Composable +private fun OtpScreenContent(realOtp: String, onOtpTextCorrectlyEntered: () -> Unit) { + Text( + text = stringResource(id = R.string.enter_otp), + color = Color(0xFF1E90FF), + fontSize = 18.sp, + style = MaterialTheme.typography.headlineMedium + ) + OtpTextField(modifier = Modifier.padding(top = 20.dp), + realOtp = realOtp, + onOtpTextCorrectlyEntered = { + onOtpTextCorrectlyEntered() + }) +} + +@Preview +@Composable +fun OtpScreenPreview() { + MifosTheme { + OtpScreen(realOtp = "1234", + contentVisibility = true, + verificationStatus = false, + onOtpTextCorrectlyEntered = {}) + } +} + +@Preview +@Composable +fun OtpScreenVerifiedPreview() { + MifosTheme { + OtpScreen(realOtp = "1234", + contentVisibility = false, + verificationStatus = true, + onOtpTextCorrectlyEntered = {}) + } +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/SetUpUPiPinScreen.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/SetUpUPiPinScreen.kt new file mode 100644 index 000000000..ef2186b26 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/SetUpUPiPinScreen.kt @@ -0,0 +1,144 @@ +package org.mifospay.bank.setupUpi.screens + +import SetUpUpiScreenContent +import android.app.Activity +import android.content.Intent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import com.mifospay.core.model.domain.BankAccountDetails +import org.mifospay.R +import org.mifospay.bank.setupUpi.viewmodel.SetUpUpiViewModal +import org.mifospay.common.Constants +import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.utils.Toaster + +@Composable +fun SetupUpiPinScreenRoute( + setUpViewModel: SetUpUpiViewModal = hiltViewModel(), + bankAccountDetails: BankAccountDetails, + type: String, + index: Int +) { + SetupUpiPinScreen( + bankAccountDetails = bankAccountDetails, + type = type, + index = index, + setupUpiPin = { + setUpViewModel.setupUpiPin(bankAccountDetails, it) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SetupUpiPinScreen( + bankAccountDetails: BankAccountDetails, + type: String, + index: Int, + setupUpiPin: (String) -> Unit +) { + val context = LocalContext.current + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = when (type) { + Constants.SETUP -> Constants.SETUP_UPI_PIN + Constants.CHANGE -> Constants.CHANGE_UPI_PIN + Constants.FORGOT -> Constants.FORGOT_UPI_PIN + else -> "" + } + ) + }, + navigationIcon = { + IconButton(onClick = { + (context as? Activity)?.onBackPressed() + }) + { + Icon( + MifosIcons.ArrowBack, + contentDescription = stringResource(id = R.string.back) + ) + } + }) + }, + content = { it -> + Column( + modifier = Modifier + .padding(it), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + SetUpUpiScreenContent( + bankAccountDetails = bankAccountDetails, + type = type, + correctlySettingUpi = { + setupUpiPin(it) + bankAccountDetails.isUpiEnabled = true + bankAccountDetails.upiPin = it + Toaster.showToast(context, Constants.UPI_PIN_SETUP_COMPLETED_SUCCESSFULLY) + val intent = Intent().apply { + putExtra(Constants.UPDATED_BANK_ACCOUNT, bankAccountDetails) + putExtra(Constants.INDEX, index) + } + (context as? Activity)?.setResult(Activity.RESULT_OK, intent) + (context as? Activity)?.finish() + } + ) + } + } + ) +} + +@Preview +@Composable +fun PreviewSetupUpiPin() { + SetupUpiPinScreen( + getBankAccountDetails(), + Constants.SETUP_UPI_PIN, + 0 + ) {} +} + +@Preview +@Composable +fun PreviewChangeUpi() { + SetupUpiPinScreen( + getBankAccountDetails(), + Constants.CHANGE, + 0 + ) {} +} + +@Preview +@Composable +fun PreviewForgetUpi() { + SetupUpiPinScreen( + getBankAccountDetails(), + Constants.FORGOT, + 0 + ) {} +} + +fun getBankAccountDetails(): BankAccountDetails { + return BankAccountDetails( + "SBI", "Ankur Sharma", "New Delhi", + "XXXXXXXX9990XXX " + " ", "Savings" + ) +} + diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/SetUpUpiScreenContent.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/SetUpUpiScreenContent.kt new file mode 100644 index 000000000..c00a959d9 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/SetUpUpiScreenContent.kt @@ -0,0 +1,134 @@ +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +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.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import org.mifos.mobilewallet.mifospay.seup_upi.fragment.DebitCardScreen +import org.mifos.mobilewallet.mifospay.seup_upi.fragment.OtpScreen +import org.mifos.mobilewallet.mifospay.seup_upi.fragment.UpiPinScreen +import org.mifospay.bank.setupUpi.viewmodel.SetUpUpiViewModal +import com.mifospay.core.model.domain.BankAccountDetails +import org.mifospay.common.Constants +import org.mifospay.utils.Toaster.showToast + +@Composable +fun SetUpUpiScreenContent( + viewModal: SetUpUpiViewModal = hiltViewModel(), + bankAccountDetails: BankAccountDetails, + type: String, + correctlySettingUpi: (String) -> Unit +) { + Column { + if (type == Constants.CHANGE) { + ChangeUpi( + viewModal.requestOtp(bankAccountDetails), correctlySettingUpi + ) + } else { + SettingAndForgotUpi( + correctlySettingUpi + ) + } + } +} + +@Composable +fun SettingAndForgotUpi( + correctlySettingUpi: (String) -> Unit +) { + var debitCardVerified by rememberSaveable { mutableStateOf(false) } + var otpVerified by rememberSaveable { mutableStateOf(false) } + var debitCardScreenVisible by rememberSaveable { mutableStateOf(true) } + var otpScreenVisible by rememberSaveable { mutableStateOf(false) } + var upiPinScreenVisible by rememberSaveable { mutableStateOf(false) } + var upiPinScreenVerified by rememberSaveable { mutableStateOf(false) } + var realOtp by rememberSaveable { mutableStateOf("") } + + val context = LocalContext.current + + DebitCardScreen(verificationStatus = debitCardVerified, + isContentVisible = debitCardScreenVisible, + onDebitCardVerified = { + debitCardVerified = true + otpScreenVisible = true + realOtp = it + debitCardScreenVisible = false + }, + onDebitCardVerificationFailed = { + showToast(context, it) + }) + OtpScreen(verificationStatus = otpVerified, + contentVisibility = otpScreenVisible, + realOtp = realOtp, + onOtpTextCorrectlyEntered = { + otpScreenVisible = false + otpVerified = true + upiPinScreenVisible = true + }) + UpiPinScreen(contentVisibility = upiPinScreenVisible, correctlySettingUpi = { + upiPinScreenVerified = true + correctlySettingUpi(it) + }) +} + +@Composable +fun ChangeUpi( + otpText: String, correctlySettingUpi: (String) -> Unit +) { + var otpVerified by rememberSaveable { mutableStateOf(false) } + var otpScreenVisible by rememberSaveable { mutableStateOf(true) } + var upiPinScreenVisible by rememberSaveable { mutableStateOf(false) } + var upiPinScreenVerified by rememberSaveable { mutableStateOf(false) } + var realOtp by rememberSaveable { mutableStateOf(otpText) } + + OtpScreen(verificationStatus = otpVerified, + contentVisibility = otpScreenVisible, + realOtp = realOtp, + onOtpTextCorrectlyEntered = { + otpScreenVisible = false + Log.i("OtpScreen", "$otpScreenVisible") + otpVerified = true + upiPinScreenVisible = true + otpScreenVisible = false + }) + UpiPinScreen(contentVisibility = upiPinScreenVisible, correctlySettingUpi = { + upiPinScreenVerified = true + correctlySettingUpi(it) + }) +} + + +@Preview +@Composable +fun PreviewSetUpUpiPin() { + MaterialTheme { + SetUpUpiScreenContent(bankAccountDetails = BankAccountDetails(), + type = Constants.SETUP, + correctlySettingUpi = {}) + } +} + +@Preview +@Composable +fun PreviewForgetUpiPin() { + MaterialTheme { + SetUpUpiScreenContent(bankAccountDetails = BankAccountDetails(), + type = Constants.FORGOT, + correctlySettingUpi = {}) + } +} + +@Preview +@Composable +fun PreviewChangeUpiPin() { + MaterialTheme { + SetUpUpiScreenContent(bankAccountDetails = BankAccountDetails(), + type = Constants.CHANGE, + correctlySettingUpi = {}) + } +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/UpiPinScreen.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/UpiPinScreen.kt new file mode 100644 index 000000000..299a9115e --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/screens/UpiPinScreen.kt @@ -0,0 +1,188 @@ +package org.mifos.mobilewallet.mifospay.seup_upi.fragment + +import androidx.compose.foundation.layout.Arrangement +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Card +import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.mifos.mobilewallet.mifospay.ui.VerifyStepHeader +import org.mifospay.R +import org.mifospay.core.designsystem.theme.MifosTheme + +@Composable +fun UpiPinScreen( + verificationStatus: Boolean = false, + contentVisibility: Boolean = false, + correctlySettingUpi: (String) -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding( + top = dimensionResource(id = R.dimen.value_15dp), + bottom = dimensionResource(id = R.dimen.value_15dp), + start = dimensionResource(id = R.dimen.value_10dp), + end = dimensionResource(id = R.dimen.value_10dp) + ), elevation = 1.dp + ) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + VerifyStepHeader(stringResource(id = R.string.upi_pin_setup), verificationStatus) + if (contentVisibility) UpiPinScreenContent(correctlySettingUpi) + } + } +} + +@Composable +fun UpiPinScreenContent( + correctlySettingUpi: (String) -> Unit +) { + var steps1 = rememberSaveable { mutableStateOf(0) } + var upiPin1 = rememberSaveable { mutableStateOf("") } + var upiPin2 = rememberSaveable { mutableStateOf("") } + var upiPinTobeMatched1 = rememberSaveable { mutableStateOf("") } + val context = LocalContext.current + val snackbarHostState = remember { SnackbarHostState() } + + Text( + text = if (steps1.value == 0) stringResource(id = R.string.enter_upi_pin) + else stringResource(id = R.string.reenter_upi), + color = colorResource(id = R.color.colorUpiPinScreenTitle), + fontSize = 18.sp, + style = MaterialTheme.typography.headlineMedium + ) + + if (steps1.value == 0) { + BasicTextField(value = upiPin1.value, onValueChange = { + upiPin1.value = it + + if (upiPin1.value.length == 4) { + steps1.value = 1 + upiPinTobeMatched1.value = upiPin1.value + } + }, keyboardActions = KeyboardActions(onDone = { + if (upiPin1.value.length == 4) { + steps1.value = 1 + upiPinTobeMatched1.value = upiPin1.value + } + }), keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), decorationBox = { + Row(horizontalArrangement = Arrangement.Center) { + repeat(4) { index -> + UpiPinCharView( + index = index, text = upiPin1.value + ) + Spacer(modifier = Modifier.width(8.dp)) + } + } + }, modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + } else { + BasicTextField(value = upiPin2.value, onValueChange = { + upiPin2.value = it + if (upiPin2.value.length == 4) { + if (upiPin1.value == upiPin2.value) { + correctlySettingUpi(upiPin2.value) + } + } + }, keyboardActions = KeyboardActions(onDone = { + if (upiPin2.value.length == 4) { + if (upiPin1.value == upiPin2.value) { + correctlySettingUpi(upiPin2.value) + } + } else { + showSnackbar(snackbarHostState, R.string.Invalid_UPI_PIN.toString()) + } + }), keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), decorationBox = { + Row(horizontalArrangement = Arrangement.Center) { + repeat(4) { index -> + UpiPinCharView( + index = index, text = upiPin2.value + ) + Spacer(modifier = Modifier.width(8.dp)) + } + } + }, modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + } +} + +fun showSnackbar(snackbarHostState: SnackbarHostState, message: String) { + CoroutineScope(Dispatchers.Main).launch { + snackbarHostState.showSnackbar(message) + } +} + +@Composable +fun UpiPinCharView( + index: Int, text: String +) { + val isFocused = text.length == index + + val char = when { + index == text.length -> "_" + index > text.length -> "_" + else -> text[index].toString() + } + androidx.compose.material3.Text( + modifier = Modifier + .width(40.dp) + .wrapContentHeight(align = Alignment.CenterVertically), + text = char, + style = MaterialTheme.typography.headlineSmall, + color = if (isFocused) { + Color.DarkGray + } else { + Color.LightGray + }, + textAlign = TextAlign.Center + ) +} + +@Preview +@Composable +fun UpiScreenPreview() { + MifosTheme { + UpiPinScreen(verificationStatus = false, contentVisibility = true, {}) + } +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/ui/SetupUpiPinActivity.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/ui/SetupUpiPinActivity.kt new file mode 100644 index 000000000..09b5775f2 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/ui/SetupUpiPinActivity.kt @@ -0,0 +1,35 @@ +package org.mifospay.bank.setupUpi.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import com.mifospay.core.model.domain.BankAccountDetails +import dagger.hilt.android.AndroidEntryPoint +import org.mifospay.bank.setupUpi.screens.SetupUpiPinScreenRoute +import org.mifospay.common.Constants +import org.mifospay.core.designsystem.theme.MifosTheme + +@AndroidEntryPoint +class SetupUpiPinActivity : ComponentActivity() { + + private var bankAccountDetails: BankAccountDetails? = null + private var index = 0 + private var type: String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val b = intent.extras + bankAccountDetails = b!!.getParcelable(Constants.BANK_ACCOUNT_DETAILS) + index = b.getInt(Constants.INDEX) + type = b.getString(Constants.TYPE) + setContent { + MifosTheme { + SetupUpiPinScreenRoute( + bankAccountDetails = bankAccountDetails!!, + type = type!!, + index = index + ) + } + } + } +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/viewmodel/DebitCardViewModal.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/viewmodel/DebitCardViewModal.kt new file mode 100644 index 000000000..19f5a795b --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/viewmodel/DebitCardViewModal.kt @@ -0,0 +1,48 @@ +package org.mifos.mobilewallet.mifospay.seup_upi.presenter + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class DebitCardViewModel @Inject constructor() : ViewModel() { + + private val _debitCardUiState = MutableStateFlow(DebitCardUiState.Initials) + val debitCardUiState: StateFlow = _debitCardUiState + + fun verifyDebitCard(debitCardNumber: String, month: String, year: String) { + val otp = "0000" + viewModelScope.launch { + _debitCardUiState.value = DebitCardUiState.Verifying + delay(2000) // Simulating a verification delay + val isVerified = verifyDebitCardNumber(debitCardNumber, month, year) + if (isVerified) { + _debitCardUiState.value = DebitCardUiState.Verified(otp) + } else { + _debitCardUiState.value = DebitCardUiState.VerificationFailed( + "Invalid Debit Card Number" + ) + + } + } + } + + private fun verifyDebitCardNumber( + debitCardNumber: String, month: String, year: String + ): Boolean { + return debitCardNumber.length in 12..19 + } + +} + +sealed class DebitCardUiState { + object Initials : DebitCardUiState() + object Verifying : DebitCardUiState() + data class Verified(val otp: String) : DebitCardUiState() + data class VerificationFailed(val errorMessage: String) : DebitCardUiState() +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/setupUpi/viewmodel/SetUpUpiViewModal.kt b/mifospay/src/main/java/org/mifospay/bank/setupUpi/viewmodel/SetUpUpiViewModal.kt new file mode 100644 index 000000000..078f14f88 --- /dev/null +++ b/mifospay/src/main/java/org/mifospay/bank/setupUpi/viewmodel/SetUpUpiViewModal.kt @@ -0,0 +1,19 @@ +package org.mifospay.bank.setupUpi.viewmodel + +import androidx.lifecycle.ViewModel +import com.mifospay.core.model.domain.BankAccountDetails +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + + +@HiltViewModel +class SetUpUpiViewModal @Inject constructor() :ViewModel() +{ + fun requestOtp(bankAccountDetails: BankAccountDetails?): String { + val otp = "0000" + return otp; + } + fun setupUpiPin(bankAccountDetails: BankAccountDetails?, mSetupUpiPin: String?) { + // to do setup upi pin api + } +} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/bank/ui/BankAccountDetailActivity.kt b/mifospay/src/main/java/org/mifospay/bank/ui/BankAccountDetailActivity.kt index 4592d9d4d..5cde19895 100644 --- a/mifospay/src/main/java/org/mifospay/bank/ui/BankAccountDetailActivity.kt +++ b/mifospay/src/main/java/org/mifospay/bank/ui/BankAccountDetailActivity.kt @@ -5,6 +5,7 @@ import android.os.Bundle import androidx.activity.compose.setContent import com.mifospay.core.model.domain.BankAccountDetails import dagger.hilt.android.AndroidEntryPoint +import org.mifospay.bank.setupUpi.ui.SetupUpiPinActivity import org.mifospay.base.BaseActivity import org.mifospay.common.Constants import org.mifospay.theme.MifosTheme diff --git a/mifospay/src/main/java/org/mifospay/bank/ui/SetupUpiPinActivity.kt b/mifospay/src/main/java/org/mifospay/bank/ui/SetupUpiPinActivity.kt deleted file mode 100644 index b29b3a5a8..000000000 --- a/mifospay/src/main/java/org/mifospay/bank/ui/SetupUpiPinActivity.kt +++ /dev/null @@ -1,144 +0,0 @@ -package org.mifospay.bank.ui - -import android.content.Intent -import android.os.Bundle -import android.view.View -import android.widget.FrameLayout -import android.widget.TextView -import androidx.appcompat.widget.Toolbar -import androidx.cardview.widget.CardView -import butterknife.BindView -import butterknife.ButterKnife -import dagger.hilt.android.AndroidEntryPoint -import com.mifospay.core.model.domain.BankAccountDetails -import org.mifospay.R -import org.mifospay.bank.BankContract -import org.mifospay.bank.BankContract.SetupUpiPinView -import org.mifospay.bank.fragment.DebitCardFragment -import org.mifospay.bank.fragment.OtpFragment -import org.mifospay.bank.fragment.UpiPinFragment -import org.mifospay.bank.presenter.SetupUpiPinPresenter -import org.mifospay.base.BaseActivity -import org.mifospay.utils.AnimationUtil -import org.mifospay.common.Constants -import org.mifospay.utils.Toaster -import javax.inject.Inject - -@AndroidEntryPoint -class SetupUpiPinActivity : BaseActivity(), SetupUpiPinView { - @JvmField - @Inject - var mPresenter: SetupUpiPinPresenter? = null - var mSetupUpiPinPresenter: BankContract.SetupUpiPinPresenter? = null - - @JvmField - @BindView(R.id.fl_debit_card) - var mFlDebitCard: FrameLayout? = null - - @JvmField - @BindView(R.id.toolbar) - var mToolbar: Toolbar? = null - - @JvmField - @BindView(R.id.fl_otp) - var mFlOtp: FrameLayout? = null - - @JvmField - @BindView(R.id.fl_upi_pin) - var mFlUpiPin: FrameLayout? = null - - @JvmField - @BindView(R.id.cv_debit_card) - var mCvDebitCard: CardView? = null - - @JvmField - @BindView(R.id.tv_debit_card) - var mTvDebitCard: TextView? = null - - @JvmField - @BindView(R.id.tv_otp) - var mTvOtp: TextView? = null - - @JvmField - @BindView(R.id.tv_upi) - var mTvUpi: TextView? = null - private var bankAccountDetails: BankAccountDetails? = null - private var index = 0 - private var type: String? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_setup_upi_pin) - ButterKnife.bind(this) - showColoredBackButton(R.drawable.ic_arrow_back_black_24dp) - setToolbarTitle(Constants.SETUP_UPI_PIN) - mPresenter!!.attachView(this) - val b = intent.extras - bankAccountDetails = b!!.getParcelable(Constants.BANK_ACCOUNT_DETAILS) - index = b.getInt(Constants.INDEX) - type = b.getString(Constants.TYPE) - if (type == Constants.CHANGE) { - mSetupUpiPinPresenter!!.requestOtp(bankAccountDetails) - mFlDebitCard!!.visibility = View.GONE - mCvDebitCard!!.visibility = View.GONE - setToolbarTitle(Constants.CHANGE_UPI_PIN) - } else if (type == Constants.FORGOT) { - mFlDebitCard!!.visibility = View.VISIBLE - mCvDebitCard!!.visibility = View.VISIBLE - setToolbarTitle(Constants.FORGOT_UPI_PIN) - } else { - mFlDebitCard!!.visibility = View.VISIBLE - mCvDebitCard!!.visibility = View.VISIBLE - setToolbarTitle(Constants.SETUP_UPI_PIN) - } - addFragment(DebitCardFragment(), R.id.fl_debit_card) - } - - override fun debitCardVerified(otp: String?) { - mTvDebitCard!!.visibility = View.VISIBLE - addFragment(OtpFragment.newInstance(otp), R.id.fl_otp) - mFlDebitCard?.let { AnimationUtil.collapse(it) } - mFlOtp?.let { AnimationUtil.expand(it) } - } - - fun otpVerified() { - mTvOtp!!.visibility = View.VISIBLE - addFragment(UpiPinFragment(), R.id.fl_upi_pin) - mFlUpiPin?.let { AnimationUtil.expand(it) } - mFlOtp?.let { AnimationUtil.collapse(it) } - } - - fun upiPinEntered(upiPin: String?) { - replaceFragment(UpiPinFragment.newInstance(1, upiPin), false, R.id.fl_upi_pin) - } - - fun upiPinConfirmed(upiPin: String?) { - mTvUpi!!.visibility = View.VISIBLE - showProgressDialog(Constants.SETTING_UP_UPI_PIN) - mSetupUpiPinPresenter!!.setupUpiPin(bankAccountDetails, upiPin) - } - - override fun setupUpiPinSuccess(mSetupUpiPin: String?) { - bankAccountDetails!!.isUpiEnabled = true - bankAccountDetails!!.upiPin = mSetupUpiPin - hideProgressDialog() - showToast(Constants.UPI_PIN_SETUP_COMPLETED_SUCCESSFULLY) - val intent = Intent() - intent.putExtra(Constants.UPDATED_BANK_ACCOUNT, bankAccountDetails) - intent.putExtra(Constants.INDEX, index) - setResult(RESULT_OK, intent) - finish() - } - - override fun setupUpiPinError(message: String?) { - hideProgressDialog() - showToast(Constants.ERROR_WHILE_SETTING_UP_UPI_PIN) - } - - override fun setPresenter(presenter: BankContract.SetupUpiPinPresenter?) { - mSetupUpiPinPresenter = presenter - } - - fun showToast(message: String?) { - Toaster.showToast(this, message) - } -} \ No newline at end of file diff --git a/mifospay/src/main/java/org/mifospay/password/ui/EditPasswordScreen.kt b/mifospay/src/main/java/org/mifospay/password/ui/EditPasswordScreen.kt index d3c4174bd..2e7693078 100644 --- a/mifospay/src/main/java/org/mifospay/password/ui/EditPasswordScreen.kt +++ b/mifospay/src/main/java/org/mifospay/password/ui/EditPasswordScreen.kt @@ -1,4 +1,4 @@ -package org.mifospay.password.ui +git sttpackage org.mifospay.password.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -104,8 +104,13 @@ fun EditPasswordScreen( SnackbarHost(hostState = snackbarHostState) }, backPress = onBackPress, - scaffoldContent = { - Column( + scaffoldContent = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + MfPasswordTextField( modifier = Modifier .fillMaxSize() .padding(it) @@ -181,6 +186,7 @@ fun EditPasswordScreen( } } } + } ) } diff --git a/mifospay/src/main/res/values/colors.xml b/mifospay/src/main/res/values/colors.xml index bd2de771a..136f9522c 100644 --- a/mifospay/src/main/res/values/colors.xml +++ b/mifospay/src/main/res/values/colors.xml @@ -70,5 +70,6 @@ #3f3f3f #2a2a2a #151515 + #1E90FF \ No newline at end of file diff --git a/mifospay/src/main/res/values/strings.xml b/mifospay/src/main/res/values/strings.xml index a08b59eb8..1aa4e6226 100644 --- a/mifospay/src/main/res/values/strings.xml +++ b/mifospay/src/main/res/values/strings.xml @@ -220,6 +220,7 @@ Wrong OTP Re-enter UPI PIN UPI PIN mismatch + Invalid UPI PIN Enter UPI PIN of length 4 No Enter a valid mobile number @@ -406,5 +407,6 @@ Merchant ID Consumer ID Invoice + Back