compress mib cards and add prep support for bml cards

This commit is contained in:
2026-05-23 21:03:25 +05:00
parent 268f3dada0
commit e9f0cec698
50 changed files with 249 additions and 66 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@@ -166,16 +166,21 @@ class BmlAccountClient {
internalId = internalId internalId = internalId
)) ))
} else if (accountType == "Card") { } else if (accountType == "Card") {
val isVisible = item.optBoolean("account_visible", false)
if (!isVisible) continue
val isPrepaid = item.optBoolean("prepaid_card", false) val isPrepaid = item.optBoolean("prepaid_card", false)
val productCode = item.optString("product_code", "")
val cardBalance = item.optJSONObject("cardBalance") val cardBalance = item.optJSONObject("cardBalance")
val available = cardBalance?.optDouble("AvailableLimit", 0.0) ?: 0.0 val available = cardBalance?.optDouble("AvailableLimit", 0.0) ?: 0.0
val current = cardBalance?.optDouble("CurrentBalance", 0.0) ?: 0.0 val current = cardBalance?.optDouble("CurrentBalance", 0.0) ?: 0.0
val cardProfileType = when {
isPrepaid -> "BML_PREPAID"
product.contains("CREDIT", ignoreCase = true) -> "BML_CREDIT"
else -> "BML_DEBIT"
}
prepaidCards.add(BankAccount( prepaidCards.add(BankAccount(
bank = "BML", bank = "BML",
profileName = profileName, profileName = profileName,
profileType = if (isPrepaid) "BML_PREPAID" else "BML_CREDIT", profileType = cardProfileType,
cifType = productCode,
accountNumber = accountNumber, accountNumber = accountNumber,
accountBriefName = item.optString("alias").ifBlank { product }, accountBriefName = item.optString("alias").ifBlank { product },
currencyName = currency, currencyName = currency,
@@ -15,8 +15,8 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import sh.sar.basedbank.R import sh.sar.basedbank.R
import sh.sar.basedbank.api.mib.MibCard
import sh.sar.basedbank.databinding.FragmentCardSettingsBinding import sh.sar.basedbank.databinding.FragmentCardSettingsBinding
import sh.sar.basedbank.util.bmlapi.BmlCardParser
class CardSettingsFragment : Fragment() { class CardSettingsFragment : Fragment() {
@@ -44,17 +44,23 @@ class CardSettingsFragment : Fragment() {
} }
binding.swipeRefresh.setOnRefreshListener { binding.swipeRefresh.setOnRefreshListener {
(activity as? HomeActivity)?.triggerRefreshCards() (activity as? HomeActivity)?.triggerRefresh()
} }
viewModel.mibCards.observe(viewLifecycleOwner) { cards -> val updateCardList = {
if (cards == null) return@observe val mibItems = (viewModel.mibCards.value ?: emptyList()).map { CardItem.Mib(it) }
adapter.update(cards) val bmlItems = (viewModel.accounts.value ?: emptyList())
.filter { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT" }
.map { CardItem.Bml(it) }
val all = mibItems + bmlItems
adapter.update(all)
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
binding.swipeRefresh.isRefreshing = false binding.swipeRefresh.isRefreshing = false
binding.emptyView.visibility = if (cards.isEmpty()) View.VISIBLE else View.GONE binding.emptyView.visibility = if (all.isEmpty()) View.VISIBLE else View.GONE
binding.recyclerView.visibility = if (cards.isEmpty()) View.GONE else View.VISIBLE binding.recyclerView.visibility = if (all.isEmpty()) View.GONE else View.VISIBLE
} }
viewModel.mibCards.observe(viewLifecycleOwner) { updateCardList() }
viewModel.accounts.observe(viewLifecycleOwner) { updateCardList() }
if (viewModel.mibCards.value == null) { if (viewModel.mibCards.value == null) {
binding.loadingView.visibility = View.VISIBLE binding.loadingView.visibility = View.VISIBLE
@@ -73,11 +79,11 @@ class CardSettingsFragment : Fragment() {
} }
private inner class CardSettingsAdapter( private inner class CardSettingsAdapter(
private var cards: List<MibCard>, private var cards: List<CardItem>,
private val context: Context private val context: Context
) : RecyclerView.Adapter<CardSettingsAdapter.VH>() { ) : RecyclerView.Adapter<CardSettingsAdapter.VH>() {
fun update(newCards: List<MibCard>) { fun update(newCards: List<CardItem>) {
cards = newCards cards = newCards
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -93,17 +99,34 @@ class CardSettingsFragment : Fragment() {
private val tvCardOwner: TextView = view.findViewById(R.id.tvCardOwner) private val tvCardOwner: TextView = view.findViewById(R.id.tvCardOwner)
private val tvCardNumber: TextView = view.findViewById(R.id.tvCardNumber) private val tvCardNumber: TextView = view.findViewById(R.id.tvCardNumber)
private val tvCardType: TextView = view.findViewById(R.id.tvCardType) private val tvCardType: TextView = view.findViewById(R.id.tvCardType)
private val tvCardStatus: TextView = view.findViewById(R.id.tvCardStatus)
private val btnChangePin: View = view.findViewById(R.id.btnChangePin) private val btnChangePin: View = view.findViewById(R.id.btnChangePin)
private val btnFreeze: View = view.findViewById(R.id.btnFreeze) private val btnFreeze: View = view.findViewById(R.id.btnFreeze)
private val btnBlock: View = view.findViewById(R.id.btnBlock) private val btnBlock: View = view.findViewById(R.id.btnBlock)
fun bind(card: MibCard) { fun bind(item: CardItem) {
tvCardOwner.text = card.cardHolderName when (item) {
tvCardNumber.text = PayWithCardFragment.formatMasked(card.maskedCardNumber) is CardItem.Mib -> {
tvCardType.text = card.cardTypeDesc tvCardOwner.text = item.card.cardHolderName
val assetPath = PayWithCardFragment.cardImageAsset(card) tvCardNumber.text = PayWithCardFragment.formatMasked(item.card.maskedCardNumber)
if (assetPath != null) PayWithCardFragment.loadCardImage(ivCardImage, assetPath) tvCardType.text = item.card.cardTypeDesc
else ivCardImage.setImageDrawable(null) val assetPath = PayWithCardFragment.cardImageAsset(item.card)
if (assetPath != null) PayWithCardFragment.loadCardImage(ivCardImage, assetPath)
else ivCardImage.setImageDrawable(null)
PayWithCardFragment.bindCardStatus(tvCardStatus, PayWithCardFragment.mibCardStatusLabel(item.card.cardStatus))
itemView.alpha = 1f
}
is CardItem.Bml -> {
tvCardOwner.text = item.account.accountBriefName
tvCardNumber.text = PayWithCardFragment.formatMasked(item.account.accountNumber)
tvCardType.text = item.account.accountTypeName
PayWithCardFragment.loadCardImage(ivCardImage, BmlCardParser.cardImageAsset(item.account))
val isActive = item.account.statusDesc.equals("Active", ignoreCase = true)
val bmlStatus = item.account.statusDesc.takeUnless { isActive }
PayWithCardFragment.bindCardStatus(tvCardStatus, bmlStatus)
itemView.alpha = if (isActive) 1f else 0.45f
}
}
val wip = View.OnClickListener { val wip = View.OnClickListener {
Toast.makeText(context, R.string.work_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.work_in_progress, Toast.LENGTH_SHORT).show()
} }
@@ -209,11 +209,11 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() {
val fromAccount = accounts.find { it.accountNumber == fromAccountNumber } val fromAccount = accounts.find { it.accountNumber == fromAccountNumber }
val fromCurrency = fromAccount?.currencyName ?: "" val fromCurrency = fromAccount?.currencyName ?: ""
val fromLoginTag = fromAccount?.loginTag ?: "" val fromLoginTag = fromAccount?.loginTag ?: ""
val fromIsCard = fromAccount?.profileType == "BML_PREPAID" || fromAccount?.profileType == "BML_CREDIT" val fromIsCard = fromAccount?.profileType == "BML_PREPAID" || fromAccount?.profileType == "BML_CREDIT" || fromAccount?.profileType == "BML_DEBIT"
if (tabTag == MY_ACCOUNTS_TAG) { if (tabTag == MY_ACCOUNTS_TAG) {
val regularAccounts = accounts.filter { it.profileType != "BML_PREPAID" && it.profileType != "BML_CREDIT" } val regularAccounts = accounts.filter { it.profileType != "BML_PREPAID" && it.profileType != "BML_CREDIT" && it.profileType != "BML_DEBIT" }
val cards = accounts.filter { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" } val cards = accounts.filter { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT" }
val filteredRegular = if (search.isBlank()) regularAccounts else regularAccounts.filter { val filteredRegular = if (search.isBlank()) regularAccounts else regularAccounts.filter {
it.accountBriefName.contains(search, ignoreCase = true) || it.accountNumber.contains(search) it.accountBriefName.contains(search, ignoreCase = true) || it.accountNumber.contains(search)
@@ -21,6 +21,7 @@ import sh.sar.basedbank.api.bml.BmlForeignLimit
import sh.sar.basedbank.api.models.BankAccount 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 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
@@ -57,11 +58,17 @@ class DashboardFragment : Fragment() {
binding.rvCards.adapter = cardAdapter binding.rvCards.adapter = cardAdapter
LinearSnapHelper().attachToRecyclerView(binding.rvCards) LinearSnapHelper().attachToRecyclerView(binding.rvCards)
viewModel.mibCards.observe(viewLifecycleOwner) { cards -> val updateCardList = {
if (cards.isNullOrEmpty()) return@observe val mibItems = (viewModel.mibCards.value ?: emptyList()).map { CardItem.Mib(it) }
cardAdapter.update(cards) val bmlItems = (viewModel.accounts.value ?: emptyList())
binding.sectionCards.visibility = View.VISIBLE .filter { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.statusDesc.equals("Active", ignoreCase = true) }
.map { CardItem.Bml(it) }
val all = mibItems + bmlItems
cardAdapter.update(all)
binding.sectionCards.visibility = if (all.isNotEmpty()) View.VISIBLE else View.GONE
} }
viewModel.mibCards.observe(viewLifecycleOwner) { updateCardList() }
viewModel.accounts.observe(viewLifecycleOwner) { updateCardList() }
val bottomPaddingBase = (16 * resources.displayMetrics.density).toInt() val bottomPaddingBase = (16 * resources.displayMetrics.density).toInt()
ViewCompat.setOnApplyWindowInsetsListener(binding.buttonBar) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.buttonBar) { v, insets ->
@@ -229,9 +236,9 @@ class DashboardFragment : Fragment() {
} }
private inner class DashboardCardAdapter : RecyclerView.Adapter<DashboardCardAdapter.VH>() { private inner class DashboardCardAdapter : RecyclerView.Adapter<DashboardCardAdapter.VH>() {
private var cards: List<MibCard> = emptyList() private var cards: List<CardItem> = emptyList()
fun update(newCards: List<MibCard>) { fun update(newCards: List<CardItem>) {
cards = newCards cards = newCards
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -249,15 +256,27 @@ class DashboardFragment : Fragment() {
private val ivCardImage: ImageView = view.findViewById(R.id.ivCardImage) private val ivCardImage: ImageView = view.findViewById(R.id.ivCardImage)
private val tvCardOwner: TextView = view.findViewById(R.id.tvCardOwner) private val tvCardOwner: TextView = view.findViewById(R.id.tvCardOwner)
private val tvCardNumber: TextView = view.findViewById(R.id.tvCardNumber) private val tvCardNumber: TextView = view.findViewById(R.id.tvCardNumber)
private val tvCardStatus: TextView = view.findViewById(R.id.tvCardStatus)
private val btnPayQr: View = view.findViewById(R.id.btnPayQr) private val btnPayQr: View = view.findViewById(R.id.btnPayQr)
private val btnPayNfc: View = view.findViewById(R.id.btnPayNfc) private val btnPayNfc: View = view.findViewById(R.id.btnPayNfc)
fun bind(card: MibCard) { fun bind(item: CardItem) {
tvCardOwner.text = card.cardHolderName when (item) {
tvCardNumber.text = PayWithCardFragment.formatMasked(card.maskedCardNumber) is CardItem.Mib -> {
val assetPath = PayWithCardFragment.cardImageAsset(card) tvCardOwner.text = item.card.cardHolderName
if (assetPath != null) PayWithCardFragment.loadCardImage(ivCardImage, assetPath) tvCardNumber.text = PayWithCardFragment.formatMasked(item.card.maskedCardNumber)
else ivCardImage.setImageDrawable(null) val assetPath = PayWithCardFragment.cardImageAsset(item.card)
if (assetPath != null) PayWithCardFragment.loadCardImage(ivCardImage, assetPath)
else ivCardImage.setImageDrawable(null)
PayWithCardFragment.bindCardStatus(tvCardStatus, PayWithCardFragment.mibCardStatusLabel(item.card.cardStatus))
}
is CardItem.Bml -> {
tvCardOwner.text = item.account.accountBriefName
tvCardNumber.text = PayWithCardFragment.formatMasked(item.account.accountNumber)
PayWithCardFragment.loadCardImage(ivCardImage, BmlCardParser.cardImageAsset(item.account))
PayWithCardFragment.bindCardStatus(tvCardStatus, null) // only Active BML cards reach dashboard
}
}
btnPayQr.setOnClickListener { btnPayQr.setOnClickListener {
Toast.makeText(requireContext(), R.string.work_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.work_in_progress, Toast.LENGTH_SHORT).show()
} }
@@ -10,6 +10,11 @@ import sh.sar.basedbank.api.models.BankContactCategory
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
sealed class CardItem {
data class Mib(val card: MibCard) : CardItem()
data class Bml(val account: BankAccount) : CardItem()
}
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
val accounts = MutableLiveData<List<BankAccount>>(emptyList()) val accounts = MutableLiveData<List<BankAccount>>(emptyList())
val financing = MutableLiveData<List<MibFinanceDeal>>(emptyList()) val financing = MutableLiveData<List<MibFinanceDeal>>(emptyList())
@@ -97,7 +97,7 @@ class PayMvQrFragment : Fragment() {
private fun setupDropdown() { private fun setupDropdown() {
viewModel.accounts.observe(viewLifecycleOwner) { accounts -> viewModel.accounts.observe(viewLifecycleOwner) { accounts ->
val eligible = accounts.filter { val eligible = accounts.filter {
it.profileType != "BML_PREPAID" && it.profileType != "BML_CREDIT" && it.profileType != "BML_LOAN" it.profileType != "BML_PREPAID" && it.profileType != "BML_CREDIT" && it.profileType != "BML_DEBIT" && it.profileType != "BML_LOAN"
} }
val adapter = QrAccountAdapter(requireContext(), eligible) val adapter = QrAccountAdapter(requireContext(), eligible)
binding.actvAccount.setAdapter(adapter) binding.actvAccount.setAdapter(adapter)
@@ -2,6 +2,7 @@ package sh.sar.basedbank.ui.home
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.GradientDrawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -16,9 +17,11 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import sh.sar.basedbank.R import sh.sar.basedbank.R
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.databinding.FragmentPayWithCardBinding import sh.sar.basedbank.databinding.FragmentPayWithCardBinding
import sh.sar.basedbank.util.CardsCache import sh.sar.basedbank.util.CardsCache
import sh.sar.basedbank.util.bmlapi.BmlCardParser
class PayWithCardFragment : Fragment() { class PayWithCardFragment : Fragment() {
@@ -46,17 +49,23 @@ class PayWithCardFragment : Fragment() {
} }
binding.swipeRefresh.setOnRefreshListener { binding.swipeRefresh.setOnRefreshListener {
(activity as? HomeActivity)?.triggerRefreshCards() (activity as? HomeActivity)?.triggerRefresh()
} }
viewModel.mibCards.observe(viewLifecycleOwner) { cards -> val updateCardList = {
if (cards == null) return@observe val mibItems = (viewModel.mibCards.value ?: emptyList()).map { CardItem.Mib(it) }
adapter.update(cards) val bmlItems = (viewModel.accounts.value ?: emptyList())
.filter { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.statusDesc.equals("Active", ignoreCase = true) }
.map { CardItem.Bml(it) }
val all = mibItems + bmlItems
adapter.update(all)
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
binding.swipeRefresh.isRefreshing = false binding.swipeRefresh.isRefreshing = false
binding.emptyView.visibility = if (cards.isEmpty()) View.VISIBLE else View.GONE binding.emptyView.visibility = if (all.isEmpty()) View.VISIBLE else View.GONE
binding.recyclerView.visibility = if (cards.isEmpty()) View.GONE else View.VISIBLE binding.recyclerView.visibility = if (all.isEmpty()) View.GONE else View.VISIBLE
} }
viewModel.mibCards.observe(viewLifecycleOwner) { updateCardList() }
viewModel.accounts.observe(viewLifecycleOwner) { updateCardList() }
val cached = CardsCache.load(requireContext()) val cached = CardsCache.load(requireContext())
if (cached.isNotEmpty()) { if (cached.isNotEmpty()) {
@@ -78,11 +87,11 @@ class PayWithCardFragment : Fragment() {
} }
private inner class CardWalletAdapter( private inner class CardWalletAdapter(
private var cards: List<MibCard>, private var cards: List<CardItem>,
private val context: Context private val context: Context
) : RecyclerView.Adapter<CardWalletAdapter.VH>() { ) : RecyclerView.Adapter<CardWalletAdapter.VH>() {
fun update(newCards: List<MibCard>) { fun update(newCards: List<CardItem>) {
cards = newCards cards = newCards
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -98,16 +107,30 @@ class PayWithCardFragment : Fragment() {
private val tvCardOwner: TextView = view.findViewById(R.id.tvCardOwner) private val tvCardOwner: TextView = view.findViewById(R.id.tvCardOwner)
private val tvCardNumber: TextView = view.findViewById(R.id.tvCardNumber) private val tvCardNumber: TextView = view.findViewById(R.id.tvCardNumber)
private val tvCardType: TextView = view.findViewById(R.id.tvCardType) private val tvCardType: TextView = view.findViewById(R.id.tvCardType)
private val tvCardStatus: TextView = view.findViewById(R.id.tvCardStatus)
private val btnPayQr: View = view.findViewById(R.id.btnPayQr) private val btnPayQr: View = view.findViewById(R.id.btnPayQr)
private val btnPayNfc: View = view.findViewById(R.id.btnPayNfc) private val btnPayNfc: View = view.findViewById(R.id.btnPayNfc)
fun bind(card: MibCard) { fun bind(item: CardItem) {
tvCardOwner.text = card.cardHolderName when (item) {
tvCardNumber.text = formatMasked(card.maskedCardNumber) is CardItem.Mib -> {
tvCardType.text = card.cardTypeDesc tvCardOwner.text = item.card.cardHolderName
val assetPath = cardImageAsset(card) tvCardNumber.text = formatMasked(item.card.maskedCardNumber)
if (assetPath != null) loadCardImage(ivCardImage, assetPath) tvCardType.text = item.card.cardTypeDesc
else ivCardImage.setImageDrawable(null) val assetPath = cardImageAsset(item.card)
if (assetPath != null) loadCardImage(ivCardImage, assetPath)
else ivCardImage.setImageDrawable(null)
bindCardStatus(tvCardStatus, mibCardStatusLabel(item.card.cardStatus))
}
is CardItem.Bml -> {
tvCardOwner.text = item.account.accountBriefName
tvCardNumber.text = formatMasked(item.account.accountNumber)
tvCardType.text = item.account.accountTypeName
loadCardImage(ivCardImage, BmlCardParser.cardImageAsset(item.account))
val bmlStatus = item.account.statusDesc.takeUnless { it.equals("Active", ignoreCase = true) }
bindCardStatus(tvCardStatus, bmlStatus)
}
}
btnPayQr.setOnClickListener { btnPayQr.setOnClickListener {
Toast.makeText(context, R.string.work_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.work_in_progress, Toast.LENGTH_SHORT).show()
} }
@@ -127,9 +150,9 @@ class PayWithCardFragment : Fragment() {
companion object { companion object {
fun cardImageAsset(card: MibCard): String? = when (card.cardType) { fun cardImageAsset(card: MibCard): String? = when (card.cardType) {
"53" -> "cards/mib/visa_black_platinum.jpg" "53" -> "cards/mib/visa_black_platinum.png"
"57" -> "cards/mib/visa_blue_everyday.jpg" "57" -> "cards/mib/visa_blue_everyday.png"
"70" -> "cards/mib/visa_business.jpg" "70" -> "cards/mib/visa_business.png"
else -> null else -> null
} }
@@ -144,6 +167,23 @@ class PayWithCardFragment : Fragment() {
} }
} }
fun mibCardStatusLabel(cardStatus: String): String? = when (cardStatus) {
"CHST0" -> null // Active — no badge
else -> cardStatus
}
fun bindCardStatus(tv: TextView, statusLabel: String?) {
if (statusLabel == null) { tv.visibility = View.GONE; return }
tv.visibility = View.VISIBLE
tv.text = statusLabel
val dp = tv.context.resources.displayMetrics.density
tv.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 12 * dp
setColor(0xCC212121.toInt())
}
}
fun formatMasked(masked: String): String { fun formatMasked(masked: String): String {
if (masked.length < 4) return masked if (masked.length < 4) return masked
return "\u2022\u2022\u2022\u2022 ${masked.takeLast(4)}" return "\u2022\u2022\u2022\u2022 ${masked.takeLast(4)}"
@@ -283,6 +283,7 @@ class TransferFragment : Fragment() {
val typeLabel = when { val typeLabel = when {
account.profileType == "BML_PREPAID" -> "Prepaid Card" account.profileType == "BML_PREPAID" -> "Prepaid Card"
account.profileType == "BML_CREDIT" -> "Credit Card" account.profileType == "BML_CREDIT" -> "Credit Card"
account.profileType == "BML_DEBIT" -> "Debit Card"
account.accountTypeName.isNotBlank() -> account.accountTypeName account.accountTypeName.isNotBlank() -> account.accountTypeName
else -> account.profileType else -> account.profileType
} }
@@ -632,7 +633,7 @@ class TransferFragment : Fragment() {
val isSrcBml = src.bank == "BML" val isSrcBml = src.bank == "BML"
val isBmlBusiness = isSrcBml && isBusinessProfile(src) // to test on personal accounts: use `isSrcBml` val isBmlBusiness = isSrcBml && isBusinessProfile(src) // to test on personal accounts: use `isSrcBml`
val isSrcCard = src.profileType == "BML_PREPAID" || src.profileType == "BML_CREDIT" val isSrcCard = src.profileType == "BML_PREPAID" || src.profileType == "BML_CREDIT" || src.profileType == "BML_DEBIT"
val isDestMib = AccountInputParser.detect(resolvedAccountNumber) == AccountInputParser.InputType.MIB_ACCOUNT val isDestMib = AccountInputParser.detect(resolvedAccountNumber) == AccountInputParser.InputType.MIB_ACCOUNT
val currency = src.currencyName.ifBlank { "MVR" } val currency = src.currencyName.ifBlank { "MVR" }
val allAccounts = viewModel.accounts.value ?: emptyList() val allAccounts = viewModel.accounts.value ?: emptyList()
@@ -860,7 +861,7 @@ class TransferFragment : Fragment() {
} }
// Determine type + credit account // Determine type + credit account
val isDestMyCard = allAccounts.any { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT") && it.accountNumber == destAccount } val isDestMyCard = allAccounts.any { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.accountNumber == destAccount }
val (transferType, creditAccount, bank) = when { val (transferType, creditAccount, bank) = when {
isSrcCard -> { isSrcCard -> {
// CAD: card → own BML account // CAD: card → own BML account
@@ -869,7 +870,7 @@ class TransferFragment : Fragment() {
} }
isDestMyCard -> { isDestMyCard -> {
// CPA: BML CASA → own card top-up // CPA: BML CASA → own card top-up
val card = allAccounts.first { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT") && it.accountNumber == destAccount } val card = allAccounts.first { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.accountNumber == destAccount }
Triple("CPA", card.internalId.ifBlank { destAccount }, null as String?) Triple("CPA", card.internalId.ifBlank { destAccount }, null as String?)
} }
isDestMib && currency == "MVR" -> Triple("DOT", destAccount, "MIB") isDestMib && currency == "MVR" -> Triple("DOT", destAccount, "MIB")
@@ -950,7 +951,7 @@ class TransferFragment : Fragment() {
return return
} }
val isDestMyCard = allAccounts.any { val isDestMyCard = allAccounts.any {
(it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT") && it.accountNumber == destAccount (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.accountNumber == destAccount
} }
val (transferType, creditAccount, bank) = when { val (transferType, creditAccount, bank) = when {
isSrcCard -> { isSrcCard -> {
@@ -959,7 +960,7 @@ class TransferFragment : Fragment() {
} }
isDestMyCard -> { isDestMyCard -> {
val card = allAccounts.first { val card = allAccounts.first {
(it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT") && it.accountNumber == destAccount (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.accountNumber == destAccount
} }
Triple("CPA", card.internalId.ifBlank { destAccount }, null as String?) Triple("CPA", card.internalId.ifBlank { destAccount }, null as String?)
} }
@@ -1337,8 +1338,8 @@ class TransferFragment : Fragment() {
) : BaseAdapter(), Filterable { ) : BaseAdapter(), Filterable {
private val items: List<Any> = buildList { private val items: List<Any> = buildList {
val regular = accounts.filter { it.profileType != "BML_PREPAID" && it.profileType != "BML_CREDIT" && it.profileType != "BML_LOAN" } val regular = accounts.filter { it.profileType != "BML_PREPAID" && it.profileType != "BML_CREDIT" && it.profileType != "BML_DEBIT" && it.profileType != "BML_LOAN" }
val cards = accounts.filter { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" } val cards = accounts.filter { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT" }
addAll(regular) addAll(regular)
if (cards.isNotEmpty()) { if (cards.isNotEmpty()) {
add(getString(R.string.cards)) add(getString(R.string.cards))
@@ -1347,7 +1348,7 @@ class TransferFragment : Fragment() {
} }
fun getAccount(position: Int): BankAccount? = (items.getOrNull(position) as? BankAccount) fun getAccount(position: Int): BankAccount? = (items.getOrNull(position) as? BankAccount)
?.takeUnless { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT") && !it.statusDesc.equals("Active", ignoreCase = true) } ?.takeUnless { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && !it.statusDesc.equals("Active", ignoreCase = true) }
override fun getCount() = items.size override fun getCount() = items.size
override fun getItem(position: Int) = items[position] override fun getItem(position: Int) = items[position]
@@ -1378,7 +1379,7 @@ class TransferFragment : Fragment() {
ItemAccountDropdownBinding.inflate(LayoutInflater.from(context), parent, false) ItemAccountDropdownBinding.inflate(LayoutInflater.from(context), parent, false)
.also { it.root.tag = it } .also { it.root.tag = it }
} }
val inactive = (acc.profileType == "BML_PREPAID" || acc.profileType == "BML_CREDIT") && !acc.statusDesc.equals("Active", ignoreCase = true) val inactive = (acc.profileType == "BML_PREPAID" || acc.profileType == "BML_CREDIT" || acc.profileType == "BML_DEBIT") && !acc.statusDesc.equals("Active", ignoreCase = true)
val isBmlAccount = acc.bank == "BML" val isBmlAccount = acc.bank == "BML"
val ownerPrefix = if (isBmlAccount && acc.profileName.isNotBlank()) "${acc.profileName} · " else "" val ownerPrefix = if (isBmlAccount && acc.profileName.isNotBlank()) "${acc.profileName} · " else ""
b.tvDropdownAccountName.text = "$ownerPrefix${acc.accountBriefName}" b.tvDropdownAccountName.text = "$ownerPrefix${acc.accountBriefName}"
@@ -64,7 +64,7 @@ class TransferHistoryFragment : Fragment() {
) { ) {
fun hasMore(): Boolean = when { fun hasMore(): Boolean = when {
account.bank == "FAHIPAY" -> fahipayTotal < 0 || fahipayNextStart < fahipayTotal account.bank == "FAHIPAY" -> fahipayTotal < 0 || fahipayNextStart < fahipayTotal
account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT" -> cardMonthOffset < 2 account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT" || account.profileType == "BML_DEBIT" -> cardMonthOffset < 2
account.bank == "BML" -> bmlTotalPages < 0 || bmlNextPage <= bmlTotalPages account.bank == "BML" -> bmlTotalPages < 0 || bmlNextPage <= bmlTotalPages
else -> mibTotalCount < 0 || mibNextStart <= mibTotalCount else -> mibTotalCount < 0 || mibNextStart <= mibTotalCount
} }
@@ -187,7 +187,7 @@ class TransferHistoryFragment : Fragment() {
async { async {
try { try {
when { when {
state.account.profileType == "BML_PREPAID" || state.account.profileType == "BML_CREDIT" -> { state.account.profileType == "BML_PREPAID" || state.account.profileType == "BML_CREDIT" || state.account.profileType == "BML_DEBIT" -> {
val session = app.bmlSessionFor(state.account) ?: return@async emptyList() val session = app.bmlSessionFor(state.account) ?: return@async emptyList()
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.add(Calendar.MONTH, -state.cardMonthOffset) cal.add(Calendar.MONTH, -state.cardMonthOffset)
@@ -21,7 +21,7 @@ import java.util.Locale
class HistoryFetcher(private val account: BankAccount) { class HistoryFetcher(private val account: BankAccount) {
private val isMib get() = account.bank == "MIB" private val isMib get() = account.bank == "MIB"
private val isBmlCard get() = account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT" private val isBmlCard get() = account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT" || account.profileType == "BML_DEBIT"
private val isBmlLoan get() = account.profileType == "BML_LOAN" private val isBmlLoan get() = account.profileType == "BML_LOAN"
private val isFahipay get() = account.bank == "FAHIPAY" private val isFahipay get() = account.bank == "FAHIPAY"
@@ -0,0 +1,45 @@
package sh.sar.basedbank.util.bmlapi
import sh.sar.basedbank.api.models.BankAccount
object BmlCardParser {
/**
* Returns the asset path for the card image.
* The product code is stored in [BankAccount.cifType] for BML Card accounts.
*/
fun cardImageAsset(account: BankAccount): String =
productCodeToAsset(account.cifType)
fun productCodeToAsset(productCode: String): String = when (productCode) {
"C8201" -> "cards/bml/master_prepaid.png"
"C8205" -> "cards/bml/master_prepaid_travel.png"
"C3007" -> "cards/bml/amex_debit_green.png"
"C1007" -> "cards/bml/visa_debit.png"
"C1003" -> "cards/bml/visa_gold.png"
"C8022" -> "cards/bml/master_gold.png"
"C1020" -> "cards/bml/visa_debit_platinum.png"
"C8902" -> "cards/bml/master_islamic.png"
"C8101" -> "cards/bml/master_masveriyaa.png"
// "?????" -> "cards/bml/amex_credit_gold.png"
// "?????" -> "cards/bml/amex_credit_green.png"
// "?????" -> "cards/bml/amex_debit_gold.png"
// "?????" -> "cards/bml/amex_platinum.png"
// "?????" -> "cards/bml/master.png"
// "?????" -> "cards/bml/master_business_debit.png"
// "?????" -> "cards/bml/master_odiveriyaa.png"
// "?????" -> "cards/bml/master_passport.png"
// "?????" -> "cards/bml/master_platinum.png"
// "?????" -> "cards/bml/master_prepaid_business.png"
// "?????" -> "cards/bml/master_world.png"
// "?????" -> "cards/bml/visa_corporate.png"
// "?????" -> "cards/bml/visa_credit.png"
// "?????" -> "cards/bml/visa_debit_generic.png"
// "?????" -> "cards/bml/visa_debit_islamic.png"
// "?????" -> "cards/bml/visa_infinite.png"
// "?????" -> "cards/bml/visa_platinum.png"
// "?????" -> "cards/bml/visa_student_black.png"
// "?????" -> "cards/bml/visa_student_blue.png"
else -> "cards/bml/defaultcard.png"
}
}
@@ -12,7 +12,7 @@ object BmlDashboardParser {
*/ */
fun displayData(account: BankAccount): AccountListDisplay? { fun displayData(account: BankAccount): AccountListDisplay? {
if (account.profileType == "BML_LOAN") return null // Loans shown on financing page only if (account.profileType == "BML_LOAN") return null // Loans shown on financing page only
val isCard = account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT" val isCard = account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT" || account.profileType == "BML_DEBIT"
return if (isCard) { return if (isCard) {
val isActive = account.statusDesc.equals("Active", ignoreCase = true) val isActive = account.statusDesc.equals("Active", ignoreCase = true)
AccountListDisplay( AccountListDisplay(
@@ -31,6 +31,21 @@
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:background="@drawable/bg_card_overlay_gradient"/> android:background="@drawable/bg_card_overlay_gradient"/>
<TextView
android:id="@+id/tvCardStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="8dp"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:textSize="9sp"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:visibility="gone"/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -32,6 +32,21 @@
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:background="@drawable/bg_card_overlay_gradient"/> android:background="@drawable/bg_card_overlay_gradient"/>
<TextView
android:id="@+id/tvCardStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="10dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:textSize="10sp"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:visibility="gone"/>
<!-- Bottom-left: card owner name + masked number --> <!-- Bottom-left: card owner name + masked number -->
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -32,6 +32,21 @@
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:background="@drawable/bg_card_overlay_gradient"/> android:background="@drawable/bg_card_overlay_gradient"/>
<TextView
android:id="@+id/tvCardStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="10dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:textSize="10sp"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:visibility="gone"/>
<!-- Bottom-left: card owner name + masked number --> <!-- Bottom-left: card owner name + masked number -->
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"