Compare commits
9
Commits
ae8ad24d13
...
v1.0.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
173c02ab8f
|
||
|
|
b37b12996f
|
||
|
|
21203b39e7
|
||
|
|
0be492ca18
|
||
|
|
973576cf6a
|
||
|
|
4523aed69e
|
||
|
|
f90d83b59e
|
||
|
|
a03b1b1682
|
||
|
|
bc958e2df6
|
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "sh.sar.basedbank"
|
applicationId = "sh.sar.basedbank"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 11
|
versionCode = 12
|
||||||
versionName = "1.0.12"
|
versionName = "1.0.13"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import sh.sar.basedbank.api.models.BankAccount
|
|||||||
import sh.sar.basedbank.databinding.FragmentBmlQrPayBinding
|
import sh.sar.basedbank.databinding.FragmentBmlQrPayBinding
|
||||||
import sh.sar.basedbank.databinding.ItemAccountDropdownBinding
|
import sh.sar.basedbank.databinding.ItemAccountDropdownBinding
|
||||||
import sh.sar.basedbank.util.CredentialStore
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
|
import sh.sar.basedbank.util.RecentPick
|
||||||
|
import sh.sar.basedbank.util.RecentsCache
|
||||||
import sh.sar.basedbank.util.Totp
|
import sh.sar.basedbank.util.Totp
|
||||||
|
|
||||||
class BmlQrPayFragment : Fragment() {
|
class BmlQrPayFragment : Fragment() {
|
||||||
@@ -150,6 +152,19 @@ class BmlQrPayFragment : Fragment() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
merchantInfo = info
|
merchantInfo = info
|
||||||
|
if (info.amount == 0.0) {
|
||||||
|
val qrUrl = arguments?.getString(ARG_QR_URL)
|
||||||
|
if (qrUrl != null) {
|
||||||
|
RecentsCache.save(requireContext(), RecentPick(
|
||||||
|
accountNumber = "bmlqr:$qrUrl",
|
||||||
|
displayName = info.merchantName,
|
||||||
|
subtitle = info.merchantAddress.ifBlank { "BML Merchant" },
|
||||||
|
colorHex = "#0066A1",
|
||||||
|
imageHash = null,
|
||||||
|
isProfileImage = false
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
populateMerchant(info)
|
populateMerchant(info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,10 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() {
|
|||||||
val account = accounts.firstOrNull { it.accountNumber == accountNumber }
|
val account = accounts.firstOrNull { it.accountNumber == accountNumber }
|
||||||
val bundle = bundleOf(KEY_ACCOUNT_NUMBER to accountNumber, KEY_LABEL to label)
|
val bundle = bundleOf(KEY_ACCOUNT_NUMBER to accountNumber, KEY_LABEL to label)
|
||||||
when {
|
when {
|
||||||
|
accountNumber.startsWith("bmlqr:") -> {
|
||||||
|
bundle.putString(KEY_SUBTITLE, "BML QR Merchant")
|
||||||
|
bundle.putString(KEY_COLOR, "#0066A1")
|
||||||
|
}
|
||||||
account != null -> {
|
account != null -> {
|
||||||
bundle.putString(KEY_SUBTITLE, "${account.accountNumber} · ${account.currencyName} ${account.availableBalance}")
|
bundle.putString(KEY_SUBTITLE, "${account.accountNumber} · ${account.currencyName} ${account.availableBalance}")
|
||||||
bundle.putString(KEY_COLOR, "#FE860E")
|
bundle.putString(KEY_COLOR, "#FE860E")
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class ContactsFragment : Fragment() {
|
|||||||
colorHex = contact.bankColor,
|
colorHex = contact.bankColor,
|
||||||
imageHash = contact.imageHash
|
imageHash = contact.imageHash
|
||||||
)
|
)
|
||||||
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, fragment)
|
(requireActivity() as HomeActivity).showWithBackStack(fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun confirmDelete(contact: ContactDisplay) {
|
private fun confirmDelete(contact: ContactDisplay) {
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import android.app.Activity
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
@@ -25,8 +25,8 @@ import sh.sar.basedbank.api.models.BankAccount
|
|||||||
import sh.sar.basedbank.api.mib.MibCard
|
import sh.sar.basedbank.api.mib.MibCard
|
||||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||||
import sh.sar.basedbank.util.bmlapi.BmlCardParser
|
import sh.sar.basedbank.util.bmlapi.BmlCardParser
|
||||||
import sh.sar.basedbank.util.PaymvQrParser
|
|
||||||
import sh.sar.basedbank.util.CredentialStore
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
|
import sh.sar.basedbank.util.PaymvQrParser
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import sh.sar.basedbank.databinding.FragmentDashboardBinding
|
import sh.sar.basedbank.databinding.FragmentDashboardBinding
|
||||||
import sh.sar.basedbank.databinding.ItemForeignLimitBinding
|
import sh.sar.basedbank.databinding.ItemForeignLimitBinding
|
||||||
@@ -36,21 +36,35 @@ class DashboardFragment : Fragment() {
|
|||||||
private var _binding: FragmentDashboardBinding? = null
|
private var _binding: FragmentDashboardBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private val viewModel: HomeViewModel by activityViewModels()
|
private val viewModel: HomeViewModel by activityViewModels()
|
||||||
|
private var pendingQrCardNumber: String? = null
|
||||||
private var pendingQrAccountNumber: String? = null
|
|
||||||
|
|
||||||
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
||||||
|
val cardNumber = pendingQrCardNumber.also { pendingQrCardNumber = null }
|
||||||
val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
|
val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
|
||||||
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
||||||
(requireActivity() as HomeActivity).navigateTo(
|
(requireActivity() as HomeActivity).navigateTo(
|
||||||
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, pendingQrAccountNumber)
|
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, cardNumber)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
val qr = PaymvQrParser.parse(raw)
|
||||||
|
if (qr?.accountNumber != null) {
|
||||||
|
Toast.makeText(requireContext(), R.string.card_qr_paymv_unsupported, Toast.LENGTH_SHORT).show()
|
||||||
|
val defaultFrom = CredentialStore(requireContext()).getDefaultAccountNumber()
|
||||||
|
(requireActivity() as HomeActivity).navigateTo(
|
||||||
|
R.id.nav_transfer, TransferFragment.newInstanceFromQr(
|
||||||
|
accountNumber = qr.accountNumber,
|
||||||
|
displayName = qr.merchantName ?: qr.accountNumber,
|
||||||
|
amount = qr.amount,
|
||||||
|
remarks = qr.purpose,
|
||||||
|
fromAccountNumber = defaultFrom
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pendingQrAccountNumber = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
@@ -402,7 +416,7 @@ class DashboardFragment : Fragment() {
|
|||||||
if (isMib) {
|
if (isMib) {
|
||||||
Toast.makeText(requireContext(), R.string.mib_qr_nfc_not_supported, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.mib_qr_nfc_not_supported, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
pendingQrAccountNumber = (item as CardItem.Bml).account.accountNumber
|
pendingQrCardNumber = (item as CardItem.Bml).account.accountNumber
|
||||||
qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java))
|
qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,18 +446,20 @@ fun applyNavLabelVisibility() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun routeSharedQrText(text: String) {
|
private fun routeSharedQrText(text: String) {
|
||||||
|
val store = CredentialStore(this)
|
||||||
val bmlUrl = sh.sar.basedbank.util.PaymvQrParser.extractBmlGatewayUrl(text)
|
val bmlUrl = sh.sar.basedbank.util.PaymvQrParser.extractBmlGatewayUrl(text)
|
||||||
if (text.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
if (text.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
||||||
navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: text))
|
navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: text, store.getDefaultCardAccountNumber()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val qr = sh.sar.basedbank.util.PaymvQrParser.parse(text)
|
val qr = sh.sar.basedbank.util.PaymvQrParser.parse(text)
|
||||||
if (qr?.accountNumber != null) {
|
if (qr?.accountNumber != null) {
|
||||||
navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromQr(
|
navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromQr(
|
||||||
accountNumber = qr.accountNumber,
|
accountNumber = qr.accountNumber,
|
||||||
displayName = qr.merchantName ?: qr.accountNumber,
|
displayName = qr.merchantName ?: qr.accountNumber,
|
||||||
amount = qr.amount,
|
amount = qr.amount,
|
||||||
remarks = qr.purpose
|
remarks = qr.purpose,
|
||||||
|
fromAccountNumber = store.getDefaultAccountNumber()
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -39,7 +36,6 @@ import sh.sar.basedbank.databinding.FragmentPayMvQrBinding
|
|||||||
import sh.sar.basedbank.util.CredentialStore
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
import sh.sar.basedbank.databinding.ItemAccountDropdownBinding
|
import sh.sar.basedbank.databinding.ItemAccountDropdownBinding
|
||||||
import sh.sar.basedbank.util.AccountListParser
|
import sh.sar.basedbank.util.AccountListParser
|
||||||
import sh.sar.basedbank.util.PaymvQrParser
|
|
||||||
import sh.sar.basedbank.util.bmlapi.BmlCardParser
|
import sh.sar.basedbank.util.bmlapi.BmlCardParser
|
||||||
import sh.sar.basedbank.util.bmlapi.BmlDashboardParser
|
import sh.sar.basedbank.util.bmlapi.BmlDashboardParser
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -56,31 +52,6 @@ class PayMvQrFragment : Fragment() {
|
|||||||
private var generateJob: Job? = null
|
private var generateJob: Job? = null
|
||||||
private val dropdownProfileImageCache = mutableMapOf<String, Bitmap>()
|
private val dropdownProfileImageCache = mutableMapOf<String, Bitmap>()
|
||||||
|
|
||||||
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
|
||||||
|
|
||||||
// BML card/gateway QR — hand off to dedicated payment screen
|
|
||||||
val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
|
|
||||||
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
|
||||||
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw))
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val qr = PaymvQrParser.parse(raw)
|
|
||||||
if (qr == null || qr.accountNumber == null) {
|
|
||||||
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
val activity = requireActivity() as HomeActivity
|
|
||||||
activity.navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromQr(
|
|
||||||
accountNumber = qr.accountNumber,
|
|
||||||
displayName = qr.merchantName ?: qr.accountNumber,
|
|
||||||
amount = qr.amount,
|
|
||||||
remarks = qr.purpose
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
@@ -106,7 +77,7 @@ class PayMvQrFragment : Fragment() {
|
|||||||
binding.btnShare.setOnClickListener { shareQr() }
|
binding.btnShare.setOnClickListener { shareQr() }
|
||||||
binding.btnSave.setOnClickListener { saveQr() }
|
binding.btnSave.setOnClickListener { saveQr() }
|
||||||
binding.btnScanQr.setOnClickListener {
|
binding.btnScanQr.setOnClickListener {
|
||||||
qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java))
|
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceWithAutoScan())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sh.sar.basedbank.ui.home
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -13,7 +14,6 @@ import android.widget.ImageView
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import android.view.animation.AccelerateInterpolator
|
import android.view.animation.AccelerateInterpolator
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -60,8 +60,37 @@ class CardsFragment : Fragment() {
|
|||||||
private var cards: List<CardItem> = emptyList()
|
private var cards: List<CardItem> = emptyList()
|
||||||
private var currentCardPosition: Int = 0
|
private var currentCardPosition: Int = 0
|
||||||
private var cardWidth: Int = 0
|
private var cardWidth: Int = 0
|
||||||
private var pendingQrAccountNumber: String? = null
|
private var pendingQrCardNumber: String? = null
|
||||||
private var isManageMode: Boolean = false
|
private var isManageMode: Boolean = false
|
||||||
|
|
||||||
|
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
|
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
||||||
|
val cardNumber = pendingQrCardNumber.also { pendingQrCardNumber = null }
|
||||||
|
val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
|
||||||
|
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
||||||
|
(requireActivity() as HomeActivity).navigateTo(
|
||||||
|
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, cardNumber)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val qr = PaymvQrParser.parse(raw)
|
||||||
|
if (qr?.accountNumber != null) {
|
||||||
|
Toast.makeText(requireContext(), R.string.card_qr_paymv_unsupported, Toast.LENGTH_SHORT).show()
|
||||||
|
val defaultFrom = store.getDefaultAccountNumber()
|
||||||
|
(requireActivity() as HomeActivity).navigateTo(
|
||||||
|
R.id.nav_transfer, TransferFragment.newInstanceFromQr(
|
||||||
|
accountNumber = qr.accountNumber,
|
||||||
|
displayName = qr.merchantName ?: qr.accountNumber,
|
||||||
|
amount = qr.amount,
|
||||||
|
remarks = qr.purpose,
|
||||||
|
fromAccountNumber = defaultFrom
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private var isTapMode: Boolean = false
|
private var isTapMode: Boolean = false
|
||||||
private var tapAnimView: NfcTapAnimationView? = null
|
private var tapAnimView: NfcTapAnimationView? = null
|
||||||
private var autoTapModeTriggered = false
|
private var autoTapModeTriggered = false
|
||||||
@@ -78,20 +107,6 @@ class CardsFragment : Fragment() {
|
|||||||
private lateinit var stackAdapter: CardStackAdapter
|
private lateinit var stackAdapter: CardStackAdapter
|
||||||
private val store by lazy { CredentialStore(requireContext()) }
|
private val store by lazy { CredentialStore(requireContext()) }
|
||||||
|
|
||||||
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
|
||||||
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
|
|
||||||
val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
|
|
||||||
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
|
|
||||||
(requireActivity() as HomeActivity).navigateTo(
|
|
||||||
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, pendingQrAccountNumber)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
pendingQrAccountNumber = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
_binding = FragmentCardsBinding.inflate(inflater, container, false)
|
_binding = FragmentCardsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
@@ -203,7 +218,7 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
|
|||||||
if (item is CardItem.Mib) {
|
if (item is CardItem.Mib) {
|
||||||
Toast.makeText(requireContext(), R.string.mib_qr_nfc_not_supported, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.mib_qr_nfc_not_supported, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
pendingQrAccountNumber = (item as CardItem.Bml).account.accountNumber
|
pendingQrCardNumber = (item as CardItem.Bml).account.accountNumber
|
||||||
qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java))
|
qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ class TransferFragment : Fragment() {
|
|||||||
// BML QR merchant payment mode (set when navigated from a card QR scan)
|
// BML QR merchant payment mode (set when navigated from a card QR scan)
|
||||||
private var bmlQrInfo: BmlQrPayInfo? = null
|
private var bmlQrInfo: BmlQrPayInfo? = null
|
||||||
private var bmlGatewayQr = false // true for pay.bml.com.mv QRs (requires pre-initiate step)
|
private var bmlGatewayQr = false // true for pay.bml.com.mv QRs (requires pre-initiate step)
|
||||||
|
private var bmlQrLookupAttempted = false // prevents re-lookup after user clears the merchant
|
||||||
private val dropdownProfileImageCache = mutableMapOf<String, Bitmap>()
|
private val dropdownProfileImageCache = mutableMapOf<String, Bitmap>()
|
||||||
|
|
||||||
// BML business profile OTP flow state
|
// BML business profile OTP flow state
|
||||||
@@ -139,6 +140,28 @@ class TransferFragment : Fragment() {
|
|||||||
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cards can't pay PayMV QR — fall back to default account or clear selection
|
||||||
|
val isCard = selectedAccount?.let {
|
||||||
|
it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT"
|
||||||
|
} ?: false
|
||||||
|
if (isCard) {
|
||||||
|
Toast.makeText(requireContext(), R.string.card_qr_paymv_unsupported, Toast.LENGTH_SHORT).show()
|
||||||
|
val defaultNum = CredentialStore(requireContext()).getDefaultAccountNumber()
|
||||||
|
val defaultAcc = defaultNum?.let { num -> viewModel.accounts.value?.firstOrNull { it.accountNumber == num } }
|
||||||
|
selectedAccount = defaultAcc
|
||||||
|
binding.tilAmount.prefixText = null
|
||||||
|
if (defaultAcc != null) {
|
||||||
|
updateAmountPrefix(defaultAcc)
|
||||||
|
showFromCard(defaultAcc)
|
||||||
|
} else {
|
||||||
|
binding.cardFromInfo.visibility = View.GONE
|
||||||
|
binding.tilFrom.visibility = View.VISIBLE
|
||||||
|
binding.actvFrom.setText("", false)
|
||||||
|
}
|
||||||
|
updateTransferButton()
|
||||||
|
}
|
||||||
|
|
||||||
if (qr.amount != null) binding.etAmount.setText(qr.amount)
|
if (qr.amount != null) binding.etAmount.setText(qr.amount)
|
||||||
if (qr.purpose != null) binding.etRemarks.setText(qr.purpose)
|
if (qr.purpose != null) binding.etRemarks.setText(qr.purpose)
|
||||||
prefillToFromContact(qr.accountNumber, "")
|
prefillToFromContact(qr.accountNumber, "")
|
||||||
@@ -191,13 +214,15 @@ class TransferFragment : Fragment() {
|
|||||||
accountNumber: String,
|
accountNumber: String,
|
||||||
displayName: String,
|
displayName: String,
|
||||||
amount: String?,
|
amount: String?,
|
||||||
remarks: String?
|
remarks: String?,
|
||||||
|
fromAccountNumber: String? = null
|
||||||
) = TransferFragment().apply {
|
) = TransferFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putString(ARG_ACCOUNT, accountNumber)
|
putString(ARG_ACCOUNT, accountNumber)
|
||||||
putString(ARG_NAME, displayName)
|
putString(ARG_NAME, displayName)
|
||||||
putString(ARG_SUBTITLE, accountNumber)
|
putString(ARG_SUBTITLE, accountNumber)
|
||||||
putString(ARG_COLOR, "#607D8B")
|
putString(ARG_COLOR, "#607D8B")
|
||||||
|
if (fromAccountNumber != null) putString(ARG_FROM_ACCOUNT, fromAccountNumber)
|
||||||
if (amount != null) putString(ARG_AMOUNT_PREFILL, amount)
|
if (amount != null) putString(ARG_AMOUNT_PREFILL, amount)
|
||||||
if (remarks != null) putString(ARG_REMARKS_PREFILL, remarks)
|
if (remarks != null) putString(ARG_REMARKS_PREFILL, remarks)
|
||||||
}
|
}
|
||||||
@@ -221,6 +246,10 @@ class TransferFragment : Fragment() {
|
|||||||
|
|
||||||
childFragmentManager.setFragmentResultListener(ContactPickerSheetFragment.REQUEST_KEY, viewLifecycleOwner) { _, bundle ->
|
childFragmentManager.setFragmentResultListener(ContactPickerSheetFragment.REQUEST_KEY, viewLifecycleOwner) { _, bundle ->
|
||||||
val accountNumber = bundle.getString(ContactPickerSheetFragment.KEY_ACCOUNT_NUMBER) ?: return@setFragmentResultListener
|
val accountNumber = bundle.getString(ContactPickerSheetFragment.KEY_ACCOUNT_NUMBER) ?: return@setFragmentResultListener
|
||||||
|
if (accountNumber.startsWith("bmlqr:")) {
|
||||||
|
lookupBmlQrMerchant(accountNumber.removePrefix("bmlqr:"))
|
||||||
|
return@setFragmentResultListener
|
||||||
|
}
|
||||||
val label = bundle.getString(ContactPickerSheetFragment.KEY_LABEL) ?: ""
|
val label = bundle.getString(ContactPickerSheetFragment.KEY_LABEL) ?: ""
|
||||||
val subtitle = bundle.getString(ContactPickerSheetFragment.KEY_SUBTITLE) ?: accountNumber
|
val subtitle = bundle.getString(ContactPickerSheetFragment.KEY_SUBTITLE) ?: accountNumber
|
||||||
val colorHex = bundle.getString(ContactPickerSheetFragment.KEY_COLOR) ?: "#607D8B"
|
val colorHex = bundle.getString(ContactPickerSheetFragment.KEY_COLOR) ?: "#607D8B"
|
||||||
@@ -268,6 +297,7 @@ class TransferFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun lookupBmlQrMerchant(qrUrl: String) {
|
private fun lookupBmlQrMerchant(qrUrl: String) {
|
||||||
|
bmlQrLookupAttempted = true
|
||||||
bmlGatewayQr = qrUrl.startsWith("https://pay.bml.com.mv/app/")
|
bmlGatewayQr = qrUrl.startsWith("https://pay.bml.com.mv/app/")
|
||||||
val base64Url = android.util.Base64.encodeToString(
|
val base64Url = android.util.Base64.encodeToString(
|
||||||
qrUrl.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP)
|
qrUrl.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP)
|
||||||
@@ -292,6 +322,16 @@ class TransferFragment : Fragment() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
bmlQrInfo = info
|
bmlQrInfo = info
|
||||||
|
if (info.amount == 0.0) {
|
||||||
|
RecentsCache.save(requireContext(), RecentPick(
|
||||||
|
accountNumber = "bmlqr:$qrUrl",
|
||||||
|
displayName = info.merchantName,
|
||||||
|
subtitle = info.merchantAddress.ifBlank { "BML Merchant" },
|
||||||
|
colorHex = "#0066A1",
|
||||||
|
imageHash = null,
|
||||||
|
isProfileImage = false
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-select the user's default BML card if no card was pre-selected
|
// Auto-select the user's default BML card if no card was pre-selected
|
||||||
if (selectedAccount == null) {
|
if (selectedAccount == null) {
|
||||||
@@ -398,6 +438,14 @@ class TransferFragment : Fragment() {
|
|||||||
updateTransferButton()
|
updateTransferButton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On a cold start (e.g. share intent), anyBmlSession() may be null when
|
||||||
|
// onViewCreated runs. Retry the lookup once sessions are available.
|
||||||
|
val pendingBmlQrUrl = arguments?.getString(ARG_BML_QR_URL)
|
||||||
|
if (pendingBmlQrUrl != null && !bmlQrLookupAttempted) {
|
||||||
|
val app = requireActivity().application as BasedBankApp
|
||||||
|
if (app.anyBmlSession() != null) lookupBmlQrMerchant(pendingBmlQrUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ object PaymvQrParser {
|
|||||||
|
|
||||||
PaymvQrData(
|
PaymvQrData(
|
||||||
accountNumber = merchantInfo?.get("03"),
|
accountNumber = merchantInfo?.get("03"),
|
||||||
amount = root["54"],
|
amount = root["54"]?.takeIf { it != "***" },
|
||||||
purpose = additionalData?.get("08"),
|
purpose = additionalData?.get("08"),
|
||||||
merchantName = root["59"]
|
merchantName = root["59"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,15 +26,6 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent">
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnTransferContact"
|
|
||||||
android:layout_width="36dp"
|
|
||||||
android:layout_height="36dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_send"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:contentDescription="@string/transfer" />
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/btnEditContact"
|
android:id="@+id/btnEditContact"
|
||||||
android:layout_width="36dp"
|
android:layout_width="36dp"
|
||||||
@@ -54,6 +45,15 @@
|
|||||||
android:tint="?attr/colorError"
|
android:tint="?attr/colorError"
|
||||||
android:contentDescription="@string/contact_delete" />
|
android:contentDescription="@string/contact_delete" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnTransferContact"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_send"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:contentDescription="@string/transfer" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -241,6 +241,7 @@
|
|||||||
<string name="transfer_scan_qr">Scan to Pay</string>
|
<string name="transfer_scan_qr">Scan to Pay</string>
|
||||||
<string name="qr_pick_image">Pick image</string>
|
<string name="qr_pick_image">Pick image</string>
|
||||||
<string name="transfer_qr_invalid">Invalid or unsupported QR code</string>
|
<string name="transfer_qr_invalid">Invalid or unsupported QR code</string>
|
||||||
|
<string name="card_qr_paymv_unsupported">PayMV QR is not supported for card payments — switching to transfer</string>
|
||||||
<string name="qr_camera_permission_title">Camera permission required</string>
|
<string name="qr_camera_permission_title">Camera permission required</string>
|
||||||
<string name="qr_camera_permission_message">Camera access is needed to scan QR codes. Please grant the permission in Settings.</string>
|
<string name="qr_camera_permission_message">Camera access is needed to scan QR codes. Please grant the permission in Settings.</string>
|
||||||
<string name="camera_permission_profile_message">Camera access is needed to take a photo. Please grant the permission in Settings.</string>
|
<string name="camera_permission_profile_message">Camera access is needed to take a photo. Please grant the permission in Settings.</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user