From 9011ef2f5a505d1f3866b270a566618615b34611 Mon Sep 17 00:00:00 2001 From: ahusan Date: Thu, 28 May 2026 14:53:45 +0500 Subject: [PATCH 1/3] debug builds: separate applicationId so they coexist with release Adds applicationIdSuffix=.debug and versionNameSuffix=-debug so a side-loaded debug build can be installed alongside the Play/release build without conflicting on package id. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 015c8f4..1666dff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,6 +27,10 @@ android { } buildTypes { + debug { + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" + } release { signingConfig = signingConfigs.getByName("release") isMinifyEnabled = false From 62ccae602d54fb4292648c18c878d7fe4b64223c Mon Sep 17 00:00:00 2001 From: ahusan Date: Thu, 28 May 2026 14:54:33 +0500 Subject: [PATCH 2/3] accounts list: use available balance, show blocked amount as secondary line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BML CASA rows on the accounts list were showing currentBalance (the working/ledger balance, which includes blocked funds). Every other balance display in the app — transfer screen, contact picker, QR pay, dashboard totals — uses availableBalance, so the same account was showing a different figure depending on where you looked at it. This switches the accounts list to availableBalance for consistency, and adds a small muted "MVR X.XX blocked" line beneath the balance when blocked > 0 so the blocked funds are still visible at a glance. Only BML reports a non-zero blocked amount; MIB and Fahipay rows are unaffected. The per-account history page header is untouched — its three-column Available / Balance / Blocked breakdown still works as before. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../sh/sar/basedbank/ui/home/AccountsAdapter.kt | 8 ++++++++ .../sh/sar/basedbank/util/AccountListDisplay.kt | 1 + .../basedbank/util/bmlapi/BmlDashboardParser.kt | 14 ++++++++------ app/src/main/res/layout/item_account.xml | 9 +++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt index 3919621..c9df329 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt @@ -125,6 +125,14 @@ class AccountsAdapter( binding.tvAccountNumber.text = display.number binding.tvAccountType.text = display.typeLabel binding.tvBalance.text = if (hideAmounts) maskAmount(display.balance) else display.balance + val blocked = display.blockedBalance + if (blocked != null) { + val shown = if (hideAmounts) maskAmount(blocked) else blocked + binding.tvBlocked.text = binding.root.context.getString(R.string.account_blocked_label, shown) + binding.tvBlocked.visibility = View.VISIBLE + } else { + binding.tvBlocked.visibility = View.GONE + } binding.btnTransfer.setOnClickListener { onTransferClick?.invoke(account) } binding.root.setOnClickListener { onAccountClick(account) } binding.root.setOnLongClickListener { diff --git a/app/src/main/java/sh/sar/basedbank/util/AccountListDisplay.kt b/app/src/main/java/sh/sar/basedbank/util/AccountListDisplay.kt index 647d04b..060a814 100644 --- a/app/src/main/java/sh/sar/basedbank/util/AccountListDisplay.kt +++ b/app/src/main/java/sh/sar/basedbank/util/AccountListDisplay.kt @@ -5,6 +5,7 @@ data class AccountListDisplay( val number: String, val typeLabel: String, val balance: String, + val blockedBalance: String? = null, // null when zero or not applicable val isCard: Boolean = false, val cardBrandIcon: Int = 0, // drawable res, only meaningful if isCard val statusLabel: String? = null // null = active; shown as status pill if set diff --git a/app/src/main/java/sh/sar/basedbank/util/bmlapi/BmlDashboardParser.kt b/app/src/main/java/sh/sar/basedbank/util/bmlapi/BmlDashboardParser.kt index a6e747b..47c2f31 100644 --- a/app/src/main/java/sh/sar/basedbank/util/bmlapi/BmlDashboardParser.kt +++ b/app/src/main/java/sh/sar/basedbank/util/bmlapi/BmlDashboardParser.kt @@ -26,11 +26,13 @@ object BmlDashboardParser { statusLabel = if (isActive) null else account.statusDesc ) } else { + val blocked = account.blockedAmount.toDoubleOrNull() ?: 0.0 AccountListDisplay( - name = account.accountBriefName, - number = account.accountNumber, - typeLabel = productLabel(account.accountTypeName), - balance = listBalance(account) + name = account.accountBriefName, + number = account.accountNumber, + typeLabel = productLabel(account.accountTypeName), + balance = listBalance(account), + blockedBalance = if (blocked > 0.0) "${account.currencyName} ${account.blockedAmount}" else null ) } } @@ -52,9 +54,9 @@ object BmlDashboardParser { } } - /** Balance shown in the accounts list — ledger (working) balance for BML CASA. */ + /** Balance shown in the accounts list — available balance, consistent with transfer/contact picker. */ fun listBalance(account: BankAccount): String = - "${account.currencyName} ${account.currentBalance}" + "${account.currencyName} ${account.availableBalance}" fun cardBrandIcon(productName: String): Int = when { productName.contains("AMEX", ignoreCase = true) || diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index 99cdbc8..6a5a514 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -72,6 +72,15 @@ android:textAppearance="?attr/textAppearanceTitleSmall" android:textColor="?attr/colorOnSurface" /> + + Accounts Cards Available Balance + %1$s blocked Quick Transfer From 8d09e760a8c36da61801bdead911c978a6e7e39b Mon Sep 17 00:00:00 2001 From: ahusan Date: Thu, 28 May 2026 15:24:49 +0500 Subject: [PATCH 3/3] Enhance dashboard: add attention row for blocked and overdue funds Introduces a new attention row in the dashboard to display blocked funds and overdue financing. The row is conditionally visible based on the presence of blocked amounts or overdue totals. Updates the account display logic to show blocked amounts where applicable, ensuring users have a clear view of their financial status. Additionally, new string resources for "Blocked Funds" and "Overdue Financing" are added for localization. --- .../sh/sar/basedbank/api/mib/MibLoginFlow.kt | 12 ++- .../basedbank/ui/home/DashboardFragment.kt | 70 ++++++++++++- .../basedbank/util/mibapi/MibAccountParser.kt | 16 +-- .../basedbank/util/mibapi/MibHistoryParser.kt | 21 ++-- .../main/res/layout/fragment_dashboard.xml | 97 +++++++++++++++++++ app/src/main/res/layout/item_account.xml | 2 +- app/src/main/res/values/strings.xml | 2 + 7 files changed, 199 insertions(+), 21 deletions(-) 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