forked from shihaam/thijooree
Able to set Default card for payments now
This commit is contained in:
@@ -25,6 +25,7 @@ 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.CredentialStore
|
||||||
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
|
||||||
@@ -101,8 +102,13 @@ class DashboardFragment : Fragment() {
|
|||||||
.filter { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.statusDesc.equals("Active", ignoreCase = true) }
|
.filter { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.statusDesc.equals("Active", ignoreCase = true) }
|
||||||
.map { CardItem.Bml(it) }
|
.map { CardItem.Bml(it) }
|
||||||
val all = mibItems + bmlItems
|
val all = mibItems + bmlItems
|
||||||
cardAdapter.update(all)
|
val defaultNum = CredentialStore(requireContext()).getDefaultCardAccountNumber()
|
||||||
binding.sectionCards.visibility = if (all.isNotEmpty()) View.VISIBLE else View.GONE
|
val ordered = if (defaultNum != null) {
|
||||||
|
val def = all.filterIsInstance<CardItem.Bml>().firstOrNull { it.account.accountNumber == defaultNum }
|
||||||
|
if (def != null) listOf(def) + all.filter { it !== def } else all
|
||||||
|
} else all
|
||||||
|
cardAdapter.update(ordered)
|
||||||
|
binding.sectionCards.visibility = if (ordered.isNotEmpty()) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
viewModel.mibCards.observe(viewLifecycleOwner) { updateCardList() }
|
viewModel.mibCards.observe(viewLifecycleOwner) { updateCardList() }
|
||||||
viewModel.accounts.observe(viewLifecycleOwner) { updateCardList() }
|
viewModel.accounts.observe(viewLifecycleOwner) { updateCardList() }
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import sh.sar.basedbank.R
|
|||||||
import sh.sar.basedbank.api.mib.MibCard
|
import sh.sar.basedbank.api.mib.MibCard
|
||||||
import sh.sar.basedbank.databinding.FragmentCardsBinding
|
import sh.sar.basedbank.databinding.FragmentCardsBinding
|
||||||
import sh.sar.basedbank.util.CardsCache
|
import sh.sar.basedbank.util.CardsCache
|
||||||
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
import sh.sar.basedbank.util.bmlapi.BmlCardParser
|
import sh.sar.basedbank.util.bmlapi.BmlCardParser
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@@ -53,6 +54,9 @@ class CardsFragment : Fragment() {
|
|||||||
private var swipeDragStartRawY = 0f
|
private var swipeDragStartRawY = 0f
|
||||||
private var swipeIsDragging = false
|
private var swipeIsDragging = false
|
||||||
|
|
||||||
|
private lateinit var stackAdapter: CardStackAdapter
|
||||||
|
private val store by lazy { CredentialStore(requireContext()) }
|
||||||
|
|
||||||
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
|
||||||
@@ -77,7 +81,7 @@ class CardsFragment : Fragment() {
|
|||||||
val peekPx = screenW / 8
|
val peekPx = screenW / 8
|
||||||
cardWidth = screenW - 2 * peekPx
|
cardWidth = screenW - 2 * peekPx
|
||||||
|
|
||||||
val stackAdapter = CardStackAdapter(cardWidth)
|
stackAdapter = CardStackAdapter(cardWidth)
|
||||||
binding.rvCards.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
binding.rvCards.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
||||||
binding.rvCards.adapter = stackAdapter
|
binding.rvCards.adapter = stackAdapter
|
||||||
binding.rvCards.setPadding(peekPx, 0, peekPx, 0)
|
binding.rvCards.setPadding(peekPx, 0, peekPx, 0)
|
||||||
@@ -114,24 +118,8 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
|
|||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
val updateCardList = {
|
viewModel.mibCards.observe(viewLifecycleOwner) { rebuildCards() }
|
||||||
val mibItems = (viewModel.mibCards.value ?: emptyList()).map { CardItem.Mib(it) }
|
viewModel.accounts.observe(viewLifecycleOwner) { rebuildCards() }
|
||||||
val bmlItems = (viewModel.accounts.value ?: emptyList())
|
|
||||||
.filter { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT" }
|
|
||||||
.map { CardItem.Bml(it) }
|
|
||||||
cards = mibItems + bmlItems
|
|
||||||
stackAdapter.update(cards)
|
|
||||||
binding.loadingView.visibility = View.GONE
|
|
||||||
val empty = cards.isEmpty()
|
|
||||||
binding.emptyView.visibility = if (empty) View.VISIBLE else View.GONE
|
|
||||||
binding.contentLayout.visibility = if (empty) View.GONE else View.VISIBLE
|
|
||||||
if (!empty) {
|
|
||||||
buildDots(cards.size, currentCardPosition)
|
|
||||||
updateCardInfo(currentCardPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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()) {
|
||||||
@@ -264,8 +252,27 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
|
|||||||
binding.pageIndicator.visibility = View.GONE
|
binding.pageIndicator.visibility = View.GONE
|
||||||
binding.llPayButtons.visibility = View.GONE
|
binding.llPayButtons.visibility = View.GONE
|
||||||
binding.llManageButtons.visibility = View.VISIBLE
|
binding.llManageButtons.visibility = View.VISIBLE
|
||||||
|
binding.llDefaultCardRow.visibility = View.VISIBLE
|
||||||
binding.manageCardView.root.visibility = View.VISIBLE
|
binding.manageCardView.root.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// Set switch state (clear listener first to avoid triggering on programmatic set)
|
||||||
|
val isBml = item is CardItem.Bml
|
||||||
|
binding.switchDefaultCard.setOnCheckedChangeListener(null)
|
||||||
|
binding.switchDefaultCard.isChecked = isBml && store.getDefaultCardAccountNumber() == (item as? CardItem.Bml)?.account?.accountNumber
|
||||||
|
binding.switchDefaultCard.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (item is CardItem.Mib) {
|
||||||
|
// MIB doesn't support NFC/QR pay — same toast as scan/tap to pay
|
||||||
|
binding.switchDefaultCard.setOnCheckedChangeListener(null)
|
||||||
|
binding.switchDefaultCard.isChecked = false
|
||||||
|
binding.switchDefaultCard.setOnCheckedChangeListener { _, c ->
|
||||||
|
handleDefaultCardToggle(c)
|
||||||
|
}
|
||||||
|
Toast.makeText(requireContext(), R.string.mib_qr_nfc_not_supported, Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
handleDefaultCardToggle(isChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// After layout pass, compute offsets, save carousel snapshot, and animate
|
// After layout pass, compute offsets, save carousel snapshot, and animate
|
||||||
binding.contentLayout.doOnNextLayout {
|
binding.contentLayout.doOnNextLayout {
|
||||||
val mgr = binding.manageCardView.root
|
val mgr = binding.manageCardView.root
|
||||||
@@ -308,6 +315,12 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleDefaultCardToggle(isChecked: Boolean) {
|
||||||
|
val item = cards.getOrNull(currentCardPosition) as? CardItem.Bml ?: return
|
||||||
|
store.setDefaultCardAccountNumber(if (isChecked) item.account.accountNumber else null)
|
||||||
|
rebuildCards()
|
||||||
|
}
|
||||||
|
|
||||||
private fun exitManageMode() {
|
private fun exitManageMode() {
|
||||||
binding.manageCardView.root.animate().cancel()
|
binding.manageCardView.root.animate().cancel()
|
||||||
binding.tvSelectedCardType.animate().cancel()
|
binding.tvSelectedCardType.animate().cancel()
|
||||||
@@ -350,6 +363,8 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
|
|||||||
binding.rvCards.visibility = View.VISIBLE
|
binding.rvCards.visibility = View.VISIBLE
|
||||||
binding.llPayButtons.visibility = View.VISIBLE
|
binding.llPayButtons.visibility = View.VISIBLE
|
||||||
binding.llManageButtons.visibility = View.GONE
|
binding.llManageButtons.visibility = View.GONE
|
||||||
|
binding.llDefaultCardRow.visibility = View.GONE
|
||||||
|
binding.switchDefaultCard.setOnCheckedChangeListener(null)
|
||||||
buildDots(cards.size, currentCardPosition)
|
buildDots(cards.size, currentCardPosition)
|
||||||
}
|
}
|
||||||
.start()
|
.start()
|
||||||
@@ -362,6 +377,42 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets ->
|
|||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun rebuildCards() {
|
||||||
|
// Remember which card is currently selected by identity so we can restore position after reorder
|
||||||
|
val currentCard = cards.getOrNull(currentCardPosition)
|
||||||
|
|
||||||
|
val defaultNum = store.getDefaultCardAccountNumber()
|
||||||
|
val mibItems = (viewModel.mibCards.value ?: emptyList()).map { CardItem.Mib(it) }
|
||||||
|
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: List<CardItem> = mibItems + bmlItems
|
||||||
|
// Move default BML card to front
|
||||||
|
cards = if (defaultNum != null) {
|
||||||
|
val def = all.filterIsInstance<CardItem.Bml>().firstOrNull { it.account.accountNumber == defaultNum }
|
||||||
|
if (def != null) listOf(def) + all.filter { it !== def } else all
|
||||||
|
} else all
|
||||||
|
|
||||||
|
// Restore position to follow the same card after reorder
|
||||||
|
if (currentCard != null) {
|
||||||
|
val newPos = cards.indexOf(currentCard)
|
||||||
|
if (newPos >= 0 && newPos != currentCardPosition) {
|
||||||
|
currentCardPosition = newPos
|
||||||
|
binding.rvCards.scrollToPosition(newPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stackAdapter.update(cards)
|
||||||
|
binding.loadingView.visibility = View.GONE
|
||||||
|
val empty = cards.isEmpty()
|
||||||
|
binding.emptyView.visibility = if (empty) View.VISIBLE else View.GONE
|
||||||
|
binding.contentLayout.visibility = if (empty) View.GONE else View.VISIBLE
|
||||||
|
if (!empty) {
|
||||||
|
buildDots(cards.size, currentCardPosition)
|
||||||
|
updateCardInfo(currentCardPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun applyCardScales() {
|
private fun applyCardScales() {
|
||||||
val rv = binding.rvCards
|
val rv = binding.rvCards
|
||||||
val rvCenter = rv.paddingStart + (rv.width - rv.paddingStart - rv.paddingEnd) / 2f
|
val rvCenter = rv.paddingStart + (rv.width - rv.paddingStart - rv.paddingEnd) / 2f
|
||||||
|
|||||||
@@ -293,6 +293,25 @@ class TransferFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
bmlQrInfo = info
|
bmlQrInfo = info
|
||||||
|
|
||||||
|
// Auto-select the user's default BML card if no card was pre-selected
|
||||||
|
if (selectedAccount == null) {
|
||||||
|
val defaultNum = CredentialStore(requireContext()).getDefaultCardAccountNumber()
|
||||||
|
if (defaultNum != null) {
|
||||||
|
val allAccounts = viewModel.accounts.value ?: emptyList()
|
||||||
|
val defaultCard = allAccounts.firstOrNull {
|
||||||
|
it.accountNumber == defaultNum &&
|
||||||
|
(it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") &&
|
||||||
|
it.statusDesc.equals("Active", ignoreCase = true)
|
||||||
|
}
|
||||||
|
if (defaultCard != null) {
|
||||||
|
selectedAccount = defaultCard
|
||||||
|
updateAmountPrefix(defaultCard)
|
||||||
|
showFromCard(defaultCard)
|
||||||
|
updateTransferButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show merchant in the "To" card — clear button hidden (can't change recipient for QR)
|
// Show merchant in the "To" card — clear button hidden (can't change recipient for QR)
|
||||||
binding.tvToAccountName.text = info.merchantName
|
binding.tvToAccountName.text = info.merchantName
|
||||||
binding.tvToBankBic.text = info.merchantAddress.ifBlank { "BML Merchant" }
|
binding.tvToBankBic.text = info.merchantAddress.ifBlank { "BML Merchant" }
|
||||||
|
|||||||
@@ -615,6 +615,18 @@ class CredentialStore(context: Context) {
|
|||||||
} catch (_: Exception) { null }
|
} catch (_: Exception) { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Default payment card ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** BML card account number the user has pinned as their default payment card, or null. */
|
||||||
|
fun getDefaultCardAccountNumber(): String? = prefs.getString("default_card_account_number", null)
|
||||||
|
|
||||||
|
fun setDefaultCardAccountNumber(accountNumber: String?) {
|
||||||
|
val editor = prefs.edit()
|
||||||
|
if (accountNumber == null) editor.remove("default_card_account_number")
|
||||||
|
else editor.putString("default_card_account_number", accountNumber)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
// ── MIB profile visibility (per loginId) ─────────────────────────────────
|
// ── MIB profile visibility (per loginId) ─────────────────────────────────
|
||||||
|
|
||||||
/** Returns the set of MIB profile IDs the user has chosen to hide (for a given loginId). */
|
/** Returns the set of MIB profile IDs the user has chosen to hide (for a given loginId). */
|
||||||
|
|||||||
@@ -147,6 +147,32 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Default card toggle (manage mode only) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llDefaultCardRow"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/card_set_as_default"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyMedium" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/switchDefaultCard"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Card management actions (manage mode only) -->
|
<!-- Card management actions (manage mode only) -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/llManageButtons"
|
android:id="@+id/llManageButtons"
|
||||||
|
|||||||
@@ -331,6 +331,7 @@
|
|||||||
<string name="card_pay_nfc">Tap to Pay</string>
|
<string name="card_pay_nfc">Tap to Pay</string>
|
||||||
<string name="mib_qr_nfc_not_supported">Skill issue on MIB side, Not supported</string>
|
<string name="mib_qr_nfc_not_supported">Skill issue on MIB side, Not supported</string>
|
||||||
<string name="card_manage">Manage Card</string>
|
<string name="card_manage">Manage Card</string>
|
||||||
|
<string name="card_set_as_default">Set as Default Card</string>
|
||||||
<string name="card_action_change_pin">Change PIN</string>
|
<string name="card_action_change_pin">Change PIN</string>
|
||||||
<string name="card_action_freeze">Freeze</string>
|
<string name="card_action_freeze">Freeze</string>
|
||||||
<string name="card_action_block">Block</string>
|
<string name="card_action_block">Block</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user