diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt index 5d556ca..49175c9 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt @@ -145,6 +145,7 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { viewModel.contacts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } viewModel.accounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } + viewModel.hideAmounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } (activity as? HomeActivity)?.loadAllContacts() } @@ -183,6 +184,7 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { private fun buildPageItems(tabTag: String?): List { val search = binding.etSheetSearch.text?.toString()?.trim() ?: "" + val hide = viewModel.hideAmounts.value ?: false val items = mutableListOf() if (tabTag == RECENTS_TAG) { @@ -223,10 +225,11 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { for (acc in filteredRegular) { if (acc.profileImageHash != null) profileImageHashes.add(acc.profileImageHash) val isSame = acc.accountNumber == fromAccountNumber + val accBal = if (hide) "••••••" else acc.availableBalance items.add(ContactPickerAdapter.PickerItem.Row( accountNumber = acc.accountNumber, displayName = acc.accountBriefName, - subtitle = "${acc.accountNumber} · ${acc.currencyName} ${acc.availableBalance}", + subtitle = "${acc.accountNumber} · ${acc.currencyName} $accBal", colorHex = "#FE860E", isSameAsFrom = isSame, imageHash = acc.profileImageHash, @@ -246,10 +249,11 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { if (acc.profileImageHash != null) profileImageHashes.add(acc.profileImageHash) val isSame = acc.accountNumber == fromAccountNumber val isActive = acc.statusDesc.equals("Active", ignoreCase = true) + val cardBal = if (hide) "••••••" else acc.availableBalance items.add(ContactPickerAdapter.PickerItem.Row( accountNumber = acc.accountNumber, displayName = acc.accountBriefName, - subtitle = "${acc.accountNumber} · ${acc.currencyName} ${acc.availableBalance}", + subtitle = "${acc.accountNumber} · ${acc.currencyName} $cardBal", colorHex = "#FE860E", isSameAsFrom = isSame, imageHash = acc.profileImageHash, @@ -306,6 +310,11 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { } } + private fun maskAmount(formatted: String): String { + val currency = formatted.substringBefore(' ', formatted) + return "$currency ••••••" + } + private fun currencyMismatchReason(fromCurrency: String, toCurrency: String): String? = if (fromCurrency == "MVR" && toCurrency == "USD") "Cannot transfer from MVR to USD account" else null diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt index 46e1e87..1193e3b 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt @@ -108,6 +108,7 @@ class TransferFragment : Fragment() { val toAvatar: Bitmap? ) private var pendingBmlTransfer: PendingBmlTransfer? = null + private var accountDropdownAdapter: AccountDropdownAdapter? = null private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult @@ -178,6 +179,11 @@ class TransferFragment : Fragment() { setupFromDropdown() setupAccountLookup() + viewModel.hideAmounts.observe(viewLifecycleOwner) { + accountDropdownAdapter?.notifyDataSetChanged() + selectedAccount?.let { showFromCard(it) } + } + childFragmentManager.setFragmentResultListener(ContactPickerSheetFragment.REQUEST_KEY, viewLifecycleOwner) { _, bundle -> val accountNumber = bundle.getString(ContactPickerSheetFragment.KEY_ACCOUNT_NUMBER) ?: return@setFragmentResultListener val label = bundle.getString(ContactPickerSheetFragment.KEY_LABEL) ?: "" @@ -236,6 +242,13 @@ class TransferFragment : Fragment() { binding.tilTo.endIconDrawable = ContextCompat.getDrawable(requireContext(), android.R.drawable.ic_menu_search) } + // ── Sensitive masking helpers ───────────────────────────────────────────── + + private fun maskAmount(formatted: String): String { + val currency = formatted.substringBefore(' ', formatted) + return "$currency ••••••" + } + private fun setupFromDropdown() { binding.btnClearFromInfo.setOnClickListener { selectedAccount = null @@ -247,11 +260,11 @@ class TransferFragment : Fragment() { } viewModel.accounts.observe(viewLifecycleOwner) { accounts -> - val adapter = AccountDropdownAdapter(requireContext(), accounts) - binding.actvFrom.setAdapter(adapter) + accountDropdownAdapter = AccountDropdownAdapter(requireContext(), accounts) + binding.actvFrom.setAdapter(accountDropdownAdapter) binding.actvFrom.setOnItemClickListener { _, _, position, _ -> - val picked = adapter.getAccount(position) ?: return@setOnItemClickListener + val picked = accountDropdownAdapter?.getAccount(position) ?: return@setOnItemClickListener selectedAccount = picked updateAmountPrefix(picked) showFromCard(picked) @@ -290,9 +303,11 @@ class TransferFragment : Fragment() { else -> account.profileType } + val hide = viewModel.hideAmounts.value ?: false + val balancePart = "${account.currencyName} ${account.availableBalance}" binding.tvFromAccountName.text = account.accountBriefName binding.tvFromAccountNumber.text = account.accountNumber - binding.tvFromAccountDetails.text = listOfNotNull(bankLabel, typeLabel, "${account.currencyName} ${account.availableBalance}").joinToString(" · ") + binding.tvFromAccountDetails.text = listOfNotNull(bankLabel, typeLabel, if (hide) maskAmount(balancePart) else balancePart).joinToString(" · ") binding.ivFromPhoto.setImageBitmap(makeInitialsBitmap(account.accountBriefName, colorHex)) binding.tilFrom.visibility = View.GONE binding.cardFromInfo.visibility = View.VISIBLE @@ -1389,9 +1404,11 @@ class TransferFragment : Fragment() { 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 ownerPrefix = if (isBmlAccount && acc.profileName.isNotBlank()) "${acc.profileName} · " else "" + val hide = viewModel.hideAmounts.value ?: false b.tvDropdownAccountName.text = "$ownerPrefix${acc.accountBriefName}" b.tvDropdownAccountNumber.text = if (inactive) "${acc.accountNumber} · ${acc.statusDesc}" else acc.accountNumber - b.tvDropdownBalance.text = AccountListParser.from(acc)?.balance ?: "" + val balance = AccountListParser.from(acc)?.balance ?: "" + b.tvDropdownBalance.text = if (hide && balance.isNotBlank()) maskAmount(balance) else balance b.root.alpha = if (inactive) 0.4f else 1f b.root }