redesign accounts page ui

This commit is contained in:
2026-05-14 05:24:31 +05:00
parent 3ddf687c8b
commit 6ed68df572
5 changed files with 176 additions and 131 deletions
@@ -187,7 +187,6 @@ class BmlLoginFlow {
val dashboard = root.optJSONObject("payload")?.optJSONArray("dashboard") ?: return emptyList() val dashboard = root.optJSONObject("payload")?.optJSONArray("dashboard") ?: return emptyList()
val casaAccounts = mutableListOf<MibAccount>() val casaAccounts = mutableListOf<MibAccount>()
val seenPrepaid = mutableSetOf<String>()
val prepaidCards = mutableListOf<MibAccount>() val prepaidCards = mutableListOf<MibAccount>()
for (i in 0 until dashboard.length()) { for (i in 0 until dashboard.length()) {
@@ -201,7 +200,7 @@ class BmlLoginFlow {
if (accountType == "CASA") { if (accountType == "CASA") {
val available = item.optDouble("availableBalance", 0.0) val available = item.optDouble("availableBalance", 0.0)
casaAccounts.add(MibAccount( casaAccounts.add(MibAccount(
profileName = "Bank of Maldives", profileName = "Personal",
profileType = "BML", profileType = "BML",
accountNumber = accountNumber, accountNumber = accountNumber,
accountBriefName = item.optString("alias"), accountBriefName = item.optString("alias"),
@@ -217,27 +216,22 @@ class BmlLoginFlow {
} else if (accountType == "Card") { } else if (accountType == "Card") {
val isPrepaid = item.optBoolean("prepaid_card", false) val isPrepaid = item.optBoolean("prepaid_card", false)
if (isPrepaid) { if (isPrepaid) {
// Deduplicate by account number, prefer Active val cardBalance = item.optJSONObject("cardBalance")
if (!seenPrepaid.contains(accountNumber) || status == "Active") { val available = cardBalance?.optDouble("AvailableLimit", 0.0) ?: 0.0
seenPrepaid.add(accountNumber) prepaidCards.add(MibAccount(
prepaidCards.removeAll { it.accountNumber == accountNumber } profileName = "Personal",
val cardBalance = item.optJSONObject("cardBalance") profileType = "BML_PREPAID",
val available = cardBalance?.optDouble("AvailableLimit", 0.0) ?: 0.0 accountNumber = accountNumber,
prepaidCards.add(MibAccount( accountBriefName = product,
profileName = "Cards", currencyName = currency,
profileType = "BML_PREPAID", accountTypeName = product,
accountNumber = accountNumber, availableBalance = "%.2f".format(available),
accountBriefName = product, currentBalance = "%.2f".format(cardBalance?.optDouble("CurrentBalance", 0.0) ?: 0.0),
currencyName = currency, blockedAmount = "0.00",
accountTypeName = product, mvrBalance = if (currency == "MVR") "%.2f".format(available) else "0.00",
availableBalance = "%.2f".format(available), statusDesc = status,
currentBalance = "%.2f".format(cardBalance?.optDouble("CurrentBalance", 0.0) ?: 0.0), profileImageHash = null
blockedAmount = "0.00", ))
mvrBalance = if (currency == "MVR") "%.2f".format(available) else "0.00",
statusDesc = status,
profileImageHash = null
))
}
} else { } else {
// Linked debit cards have no independent balance or account link — skip // Linked debit cards have no independent balance or account link — skip
} }
@@ -1,10 +1,14 @@
package sh.sar.basedbank.ui.home package sh.sar.basedbank.ui.home
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import sh.sar.basedbank.api.mib.MibAccount import sh.sar.basedbank.api.mib.MibAccount
import sh.sar.basedbank.databinding.ItemAccountBinding import sh.sar.basedbank.databinding.ItemAccountBinding
@@ -15,7 +19,7 @@ class AccountsAdapter(accounts: List<MibAccount>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private sealed class Item { private sealed class Item {
data class Header(val profileName: String, val profileType: String) : Item() data class SectionTitle(val label: String, val chip: String) : Item()
data class Account(val account: MibAccount) : Item() data class Account(val account: MibAccount) : Item()
data class Card(val account: MibAccount) : Item() data class Card(val account: MibAccount) : Item()
} }
@@ -29,30 +33,29 @@ class AccountsAdapter(accounts: List<MibAccount>) :
} }
private fun buildItems(accounts: List<MibAccount>): List<Item> = buildList { private fun buildItems(accounts: List<MibAccount>): List<Item> = buildList {
var lastProfile = "" val regular = accounts.filter { it.profileType != "BML_PREPAID" }
for (account in accounts) { val prepaid = accounts.filter { it.profileType == "BML_PREPAID" }
if (account.profileName != lastProfile) {
add(Item.Header(account.profileName, account.profileType)) if (regular.isNotEmpty()) {
lastProfile = account.profileName add(Item.SectionTitle("Accounts", ""))
} regular.forEach { add(Item.Account(it)) }
if (account.profileType == "BML_PREPAID") { }
add(Item.Card(account)) if (prepaid.isNotEmpty()) {
} else { add(Item.SectionTitle("Cards", "BML"))
add(Item.Account(account)) prepaid.forEach { add(Item.Card(it)) }
}
} }
} }
override fun getItemViewType(position: Int) = when (items[position]) { override fun getItemViewType(position: Int) = when (items[position]) {
is Item.Header -> TYPE_HEADER is Item.SectionTitle -> TYPE_HEADER
is Item.Account -> TYPE_ACCOUNT is Item.Account -> TYPE_ACCOUNT
is Item.Card -> TYPE_CARD is Item.Card -> TYPE_CARD
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (viewType) { return when (viewType) {
TYPE_HEADER -> HeaderViewHolder(ItemProfileHeaderBinding.inflate(inflater, parent, false)) TYPE_HEADER -> SectionViewHolder(ItemProfileHeaderBinding.inflate(inflater, parent, false))
TYPE_CARD -> CardViewHolder(ItemCardBinding.inflate(inflater, parent, false)) TYPE_CARD -> CardViewHolder(ItemCardBinding.inflate(inflater, parent, false))
else -> AccountViewHolder(ItemAccountBinding.inflate(inflater, parent, false)) else -> AccountViewHolder(ItemAccountBinding.inflate(inflater, parent, false))
} }
@@ -60,23 +63,23 @@ class AccountsAdapter(accounts: List<MibAccount>) :
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (val item = items[position]) { when (val item = items[position]) {
is Item.Header -> (holder as HeaderViewHolder).bind(item) is Item.SectionTitle -> (holder as SectionViewHolder).bind(item)
is Item.Account -> (holder as AccountViewHolder).bind(item.account) is Item.Account -> (holder as AccountViewHolder).bind(item.account)
is Item.Card -> (holder as CardViewHolder).bind(item.account) is Item.Card -> (holder as CardViewHolder).bind(item.account)
} }
} }
override fun getItemCount() = items.size override fun getItemCount() = items.size
private inner class HeaderViewHolder(private val binding: ItemProfileHeaderBinding) : private inner class SectionViewHolder(private val binding: ItemProfileHeaderBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item.Header) { fun bind(item: Item.SectionTitle) {
binding.tvProfileName.text = item.profileName binding.tvProfileName.text = item.label
binding.tvProfileType.text = when (item.profileType) { if (item.chip.isNotEmpty()) {
"BML" -> "Bank of Maldives" binding.tvProfileType.text = item.chip
"BML_PREPAID" -> "BML" binding.tvProfileType.visibility = View.VISIBLE
"0" -> "Personal" } else {
else -> "Business" binding.tvProfileType.visibility = View.GONE
} }
} }
} }
@@ -84,11 +87,20 @@ class AccountsAdapter(accounts: List<MibAccount>) :
private inner class AccountViewHolder(private val binding: ItemAccountBinding) : private inner class AccountViewHolder(private val binding: ItemAccountBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(account: MibAccount) { fun bind(account: MibAccount) {
binding.tvAccountName.text = account.accountBriefName binding.tvAccountName.text = account.accountBriefName
binding.tvAccountNumber.text = account.accountNumber binding.tvAccountNumber.text = account.accountNumber
binding.tvPillBank.text = if (account.profileType.startsWith("BML")) "BML" else "MIB"
binding.tvPillType.text = friendlyAccountType(account.accountTypeName)
binding.tvPillProfile.text = when (account.profileType) {
"0" -> "Personal"
"1" -> "Business"
else -> account.profileName
}
binding.tvBalance.text = "${account.currencyName} ${account.availableBalance}" binding.tvBalance.text = "${account.currencyName} ${account.availableBalance}"
binding.tvAccountType.text = account.accountTypeName binding.root.setOnLongClickListener {
binding.tvStatus.text = account.statusDesc copyToClipboard(it.context, account.accountNumber)
true
}
} }
} }
@@ -97,24 +109,25 @@ class AccountsAdapter(accounts: List<MibAccount>) :
fun bind(account: MibAccount) { fun bind(account: MibAccount) {
val brand = cardBrand(account.accountTypeName) val brand = cardBrand(account.accountTypeName)
binding.tvCardBrand.text = brand.label binding.tvCardBrand.text = brand.label
setBrandBackground(brand.color) binding.tvCardBrand.background = GradientDrawable().apply {
binding.tvCardName.text = account.accountBriefName
binding.tvCardNumber.text = account.accountNumber
val isPrepaid = account.profileType == "BML_PREPAID"
binding.layoutCardBalance.visibility = if (isPrepaid) View.VISIBLE else View.GONE
if (isPrepaid) {
binding.tvCardBalance.text = "${account.currencyName} ${account.availableBalance}"
}
}
private fun setBrandBackground(colorHex: String) {
val drawable = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE shape = GradientDrawable.RECTANGLE
cornerRadius = 100f cornerRadius = 100f
setColor(Color.parseColor(colorHex)) setColor(Color.parseColor(brand.color))
}
binding.tvCardName.text = account.accountBriefName
binding.tvCardNumber.text = account.accountNumber
binding.layoutCardBalance.visibility = View.VISIBLE
binding.tvCardBalance.text = "${account.currencyName} ${account.availableBalance}"
val isActive = account.statusDesc.equals("Active", ignoreCase = true)
if (isActive) {
binding.tvCardStatus.visibility = View.GONE
binding.root.alpha = 1f
} else {
binding.tvCardStatus.text = account.statusDesc
binding.tvCardStatus.visibility = View.VISIBLE
binding.root.alpha = 0.45f
} }
binding.tvCardBrand.background = drawable
} }
} }
@@ -123,6 +136,27 @@ class AccountsAdapter(accounts: List<MibAccount>) :
private const val TYPE_ACCOUNT = 1 private const val TYPE_ACCOUNT = 1
private const val TYPE_CARD = 2 private const val TYPE_CARD = 2
private fun copyToClipboard(context: Context, accountNumber: String) {
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("Account Number", accountNumber))
Toast.makeText(context, "Account number copied", Toast.LENGTH_SHORT).show()
}
private fun friendlyAccountType(raw: String): String {
val u = raw.trim().uppercase()
return when {
u == "SAVINGS ACCOUNT" ||
u == "SAVING ACCOUNT" -> "Savings"
u == "CURRENT ACCOUNT" ||
u == "CURRENT ACCOUNT(PERSONAL)" ||
u == "CURRENT ACCOUNT(BUSINESS)" -> "Current"
u == "WADIAH RETAIL CURRENT ACCOUNT" ||
u == "WADIAH BUSINESS CURRENT ACCOUNT" -> "Islamic Current"
u == "BML ISLAMIC SAVINGS ACCOUNT" -> "Islamic Savings"
else -> raw.trim()
}
}
private data class Brand(val label: String, val color: String) private data class Brand(val label: String, val color: String)
private fun cardBrand(productName: String): Brand = when { private fun cardBrand(productName: String): Brand = when {
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="100dp" />
<stroke android:width="1dp" android:color="?attr/colorOutline" />
</shape>
+63 -57
View File
@@ -13,92 +13,98 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="horizontal"
android:padding="20dp"> android:padding="20dp"
android:gravity="center_vertical">
<!-- Left: name / number -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_weight="1"
android:gravity="center_vertical" android:orientation="vertical">
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvAccountName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurface" />
<TextView
android:id="@+id/tvAccountNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginTop="2dp" />
</LinearLayout>
<TextView <TextView
android:id="@+id/tvStatus" android:id="@+id/tvAccountName"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="10dp" android:textAppearance="?attr/textAppearanceTitleMedium"
android:paddingVertical="4dp" android:textColor="?attr/colorOnSurface" />
android:background="@drawable/chip_background"
android:textAppearance="?attr/textAppearanceLabelSmall" <TextView
android:textColor="?attr/colorOnSecondaryContainer" /> android:id="@+id/tvAccountNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:fontFamily="monospace"
android:layout_marginTop="2dp" />
</LinearLayout> </LinearLayout>
<View <!-- Right: segmented pill (bank | type | profile) + balance -->
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorOutlineVariant"
android:layout_marginBottom="12dp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="vertical"
android:gravity="center_vertical"> android:gravity="end"
android:layout_marginStart="16dp">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:orientation="horizontal"
android:orientation="vertical"> android:gravity="center_vertical"
android:background="@drawable/pill_segment_bg">
<TextView <TextView
android:id="@+id/tvPillBank"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/available_balance" android:paddingStart="12dp"
android:paddingEnd="10dp"
android:paddingVertical="6dp"
android:textAppearance="?attr/textAppearanceLabelSmall" android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurfaceVariant" /> android:textColor="?attr/colorOnSurface" />
<View
android:layout_width="1dp"
android:layout_height="16dp"
android:background="?attr/colorOutline" />
<TextView <TextView
android:id="@+id/tvBalance" android:id="@+id/tvPillType"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleMedium" android:paddingHorizontal="10dp"
android:textColor="?attr/colorOnSurface" android:paddingVertical="6dp"
android:layout_marginTop="2dp" /> android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurface" />
<View
android:layout_width="1dp"
android:layout_height="16dp"
android:background="?attr/colorOutline" />
<TextView
android:id="@+id/tvPillProfile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingEnd="12dp"
android:paddingVertical="6dp"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurface" />
</LinearLayout> </LinearLayout>
<TextView <TextView
android:id="@+id/tvAccountType" android:id="@+id/tvBalance"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurfaceVariant" /> android:textColor="?attr/colorOnSurface"
android:layout_marginTop="6dp" />
</LinearLayout> </LinearLayout>
+11 -6
View File
@@ -41,35 +41,40 @@
android:id="@+id/tvCardName" android:id="@+id/tvCardName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnSurface" /> android:textColor="?attr/colorOnSurface" />
<TextView <TextView
android:id="@+id/tvCardNumber" android:id="@+id/tvCardNumber"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurfaceVariant" android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:fontFamily="monospace" /> android:fontFamily="monospace" />
</LinearLayout> </LinearLayout>
<!-- Balance (prepaid only) --> <!-- Status pill + balance (prepaid only) -->
<LinearLayout <LinearLayout
android:id="@+id/layoutCardBalance" android:id="@+id/layoutCardBalance"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:gravity="end" android:gravity="end"
android:layout_marginStart="16dp"
android:visibility="gone"> android:visibility="gone">
<TextView <TextView
android:id="@+id/tvCardStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/available_balance" android:paddingHorizontal="8dp"
android:paddingVertical="3dp"
android:textAppearance="?attr/textAppearanceLabelSmall" android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurfaceVariant" /> android:textColor="?attr/colorOnSurfaceVariant"
android:background="@drawable/pill_segment_bg"
android:visibility="gone" />
<TextView <TextView
android:id="@+id/tvCardBalance" android:id="@+id/tvCardBalance"
@@ -77,7 +82,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:layout_marginTop="2dp" /> android:layout_marginTop="6dp" />
</LinearLayout> </LinearLayout>