diff --git a/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt b/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt index 5a14071..4979253 100644 --- a/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt +++ b/app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt @@ -9,6 +9,7 @@ import org.json.JSONObject import java.security.MessageDigest import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean +import kotlin.math.abs import kotlin.random.Random class SessionExpiredException : Exception("MIB session expired") @@ -168,7 +169,7 @@ class MibLoginFlow(private val credentialStore: CredentialStore) { accountTypeName = a.optString("accountTypeName"), availableBalance = a.optString("availableBalance"), currentBalance = a.optString("currentBalance"), - blockedAmount = a.optString("blockedAmount"), + blockedAmount = absBlockedAmount(a.optString("blockedAmount")), mvrBalance = a.optString("mvrBalance"), statusDesc = a.optString("statusDesc"), profileImageHash = profile.customerImage, @@ -188,6 +189,13 @@ class MibLoginFlow(private val credentialStore: CredentialStore) { // ─── Helpers ───────────────────────────────────────────────────────────── + /** MIB returns blockedAmount as a signed decimal where negative = funds held. + * Normalize to a positive magnitude so downstream code can treat it uniformly. */ + private fun absBlockedAmount(raw: String): String { + val v = raw.toDoubleOrNull() ?: return raw + return "%.2f".format(abs(v)) + } + private fun initialKeyExchange( appId: String, encKey: String, sfunc: String, key2: String? = null ): Pair { @@ -325,7 +333,7 @@ class MibLoginFlow(private val credentialStore: CredentialStore) { accountTypeName = a.optString("accountTypeName"), availableBalance = a.optString("availableBalance"), currentBalance = a.optString("currentBalance"), - blockedAmount = a.optString("blockedAmount"), + blockedAmount = absBlockedAmount(a.optString("blockedAmount")), mvrBalance = a.optString("mvrBalance"), statusDesc = a.optString("statusDesc"), profileImageHash = profile.customerImage, diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt index 8fbef5f..cfb38cd 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt @@ -57,14 +57,24 @@ class DashboardFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - viewModel.accounts.observe(viewLifecycleOwner) { updateBalances(it) } - viewModel.financing.observe(viewLifecycleOwner) { updatePendingFinances() } - viewModel.bmlLoanDetails.observe(viewLifecycleOwner) { updatePendingFinances() } + viewModel.accounts.observe(viewLifecycleOwner) { + updateBalances(it) + updateAttentionRow() + } + viewModel.financing.observe(viewLifecycleOwner) { + updatePendingFinances() + updateAttentionRow() + } + viewModel.bmlLoanDetails.observe(viewLifecycleOwner) { + updatePendingFinances() + updateAttentionRow() + } viewModel.bmlLimits.observe(viewLifecycleOwner) { updateForeignLimits(it) } viewModel.hideAmounts.observe(viewLifecycleOwner) { updateBalances(viewModel.accounts.value ?: emptyList()) updatePendingFinances() updateForeignLimits(viewModel.bmlLimits.value ?: emptyList()) + updateAttentionRow() } binding.swipeRefresh.setOnRefreshListener { @@ -76,6 +86,10 @@ class DashboardFragment : Fragment() { (activity as? HomeActivity)?.navigateTo(R.id.nav_finances) } + binding.cardOverdue.setOnClickListener { + (activity as? HomeActivity)?.navigateTo(R.id.nav_finances) + } + val cardAdapter = DashboardCardAdapter() binding.rvCards.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) binding.rvCards.adapter = cardAdapter @@ -244,6 +258,56 @@ class DashboardFragment : Fragment() { } } + private fun updateAttentionRow() { + val hide = viewModel.hideAmounts.value ?: false + val accounts = viewModel.accounts.value ?: emptyList() + + // Blocked: sum across CASA-style accounts (exclude cards and loans) per currency. + val blockedByCurrency = accounts + .filter { it.profileType != "BML_CREDIT" && it.profileType != "BML_PREPAID" && it.profileType != "BML_DEBIT" && it.profileType != "BML_LOAN" } + .mapNotNull { acc -> + val v = acc.blockedAmount.replace(",", "").toDoubleOrNull() ?: 0.0 + if (v > 0.0) acc.currencyName.uppercase() to v else null + } + .groupBy({ it.first }, { it.second }) + .mapValues { (_, vs) -> vs.sum() } + + val blockedTotal = blockedByCurrency.values.sum() + if (blockedTotal > 0.0) { + // Primary line: prefer MVR if present, otherwise the first currency. + val primaryCcy = if ("MVR" in blockedByCurrency) "MVR" else blockedByCurrency.keys.first() + val primaryAmt = blockedByCurrency.getValue(primaryCcy) + binding.tvBlockedTotal.text = if (hide) "$primaryCcy ••••••" else "$primaryCcy %,.2f".format(primaryAmt) + + val secondary = blockedByCurrency.filterKeys { it != primaryCcy } + if (secondary.isNotEmpty()) { + binding.tvBlockedSecondary.text = secondary.entries.joinToString(" · ") { (ccy, amt) -> + if (hide) "$ccy ••••••" else "$ccy %,.2f".format(amt) + } + binding.tvBlockedSecondary.visibility = View.VISIBLE + } else { + binding.tvBlockedSecondary.visibility = View.GONE + } + binding.cardBlocked.visibility = View.VISIBLE + } else { + binding.cardBlocked.visibility = View.GONE + } + + // Overdue: MIB finance deals + BML loan details (assumed MVR — matches existing Pending Finances). + val mibOverdue = (viewModel.financing.value ?: emptyList()).sumOf { it.overdueAmount } + val bmlOverdue = (viewModel.bmlLoanDetails.value ?: emptyMap()).values.sumOf { it.overdueAmount } + val overdueTotal = mibOverdue + bmlOverdue + if (overdueTotal > 0.0) { + binding.tvOverdueTotal.text = if (hide) "MVR ••••••" else "MVR %,.2f".format(overdueTotal) + binding.cardOverdue.visibility = View.VISIBLE + } else { + binding.cardOverdue.visibility = View.GONE + } + + binding.rowAttention.visibility = + if (blockedTotal > 0.0 || overdueTotal > 0.0) View.VISIBLE else View.GONE + } + private fun updatePendingFinances() { val hide = viewModel.hideAmounts.value ?: false val mibTotal = (viewModel.financing.value ?: emptyList()).sumOf { it.outstandingAmount } diff --git a/app/src/main/java/sh/sar/basedbank/util/mibapi/MibAccountParser.kt b/app/src/main/java/sh/sar/basedbank/util/mibapi/MibAccountParser.kt index 717bcf5..d3d7a48 100644 --- a/app/src/main/java/sh/sar/basedbank/util/mibapi/MibAccountParser.kt +++ b/app/src/main/java/sh/sar/basedbank/util/mibapi/MibAccountParser.kt @@ -5,12 +5,16 @@ import sh.sar.basedbank.util.AccountListDisplay object MibAccountParser { - fun displayData(account: BankAccount) = AccountListDisplay( - name = account.accountBriefName, - number = account.accountNumber, - typeLabel = productLabel(account.accountTypeName), - balance = "${account.currencyName} ${account.availableBalance}" - ) + fun displayData(account: BankAccount): AccountListDisplay { + val blocked = account.blockedAmount.toDoubleOrNull() ?: 0.0 + return AccountListDisplay( + name = account.accountBriefName, + number = account.accountNumber, + typeLabel = productLabel(account.accountTypeName), + balance = "${account.currencyName} ${account.availableBalance}", + blockedBalance = if (blocked > 0.0) "${account.currencyName} ${account.blockedAmount}" else null + ) + } /** * Returns a display-ready product label for a MIB (Faisanet) account type name. diff --git a/app/src/main/java/sh/sar/basedbank/util/mibapi/MibHistoryParser.kt b/app/src/main/java/sh/sar/basedbank/util/mibapi/MibHistoryParser.kt index b7197c5..b1b60d0 100644 --- a/app/src/main/java/sh/sar/basedbank/util/mibapi/MibHistoryParser.kt +++ b/app/src/main/java/sh/sar/basedbank/util/mibapi/MibHistoryParser.kt @@ -5,13 +5,16 @@ import sh.sar.basedbank.util.AccountHistoryDisplay object MibHistoryParser { - fun displayData(account: BankAccount) = AccountHistoryDisplay( - name = account.accountBriefName, - number = account.accountNumber, - bankPill = null, // MIB has no bank pill - typeLabel = MibAccountParser.productLabel(account.accountTypeName), - availableBalance = "${account.currencyName} ${account.availableBalance}", - workingBalance = "${account.currencyName} ${account.currentBalance}", - blockedBalance = null - ) + fun displayData(account: BankAccount): AccountHistoryDisplay { + val blocked = account.blockedAmount.toDoubleOrNull() ?: 0.0 + return AccountHistoryDisplay( + name = account.accountBriefName, + number = account.accountNumber, + bankPill = null, // MIB has no bank pill + typeLabel = MibAccountParser.productLabel(account.accountTypeName), + availableBalance = "${account.currencyName} ${account.availableBalance}", + workingBalance = "${account.currencyName} ${account.currentBalance}", + blockedBalance = if (blocked > 0.0) "${account.currencyName} ${account.blockedAmount}" else null + ) + } } diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index d25b79b..f825edf 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -176,6 +176,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cards Available Balance %1$s blocked + Blocked Funds + Overdue Financing Quick Transfer