7 Commits
Author SHA1 Message Date
shihaam 1753d648bd loading indicator for account info fetch and tranfer
Auto Tag on Version Change / check-version (push) Successful in 4s
2026-05-18 05:31:20 +05:00
shihaam 423b0bf1e1 dark theme on bml recipt and hide bottom nav bar on recipt page 2026-05-18 05:21:46 +05:00
shihaam d713047970 keep bottom bar behind keyboard
Auto Tag on Version Change / check-version (push) Successful in 3s
2026-05-18 05:12:49 +05:00
shihaam d59a6fad82 new feature: resume last activity after unlocking instead of goind to dashboard everytime
Auto Tag on Version Change / check-version (push) Successful in 5s
2026-05-18 05:09:42 +05:00
shihaam 8e47101401 refactor: extract BML/MIB product label parsing into dedicated parser utilities
Auto Tag on Version Change / check-version (push) Successful in 4s
2026-05-18 04:53:40 +05:00
shihaam 9431a90cd0 add card icons
Auto Tag on Version Change / check-version (push) Successful in 3s
2026-05-18 04:35:20 +05:00
shihaam 3a10f36c39 fix prod build
Auto Tag on Version Change / check-version (push) Successful in 5s
2026-05-18 00:43:05 +05:00
16 changed files with 201 additions and 71 deletions
+3
View File
@@ -64,6 +64,9 @@ dependencies {
// RecyclerView for accounts list
implementation("androidx.recyclerview:recyclerview:1.3.2")
// CircularProgressDrawable for spinning search icons
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
// OkHttp for API calls
implementation("com.squareup.okhttp3:okhttp:4.11.0")
+2 -1
View File
@@ -48,7 +48,8 @@
<activity
android:name=".ui.home.HomeActivity"
android:exported="false" />
android:exported="false"
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".ui.home.QrScannerActivity"
@@ -39,6 +39,7 @@ class LockActivity : AppCompatActivity() {
companion object {
private const val MAX_ATTEMPTS = 5
private const val LOCKOUT_MS = 30_000L
const val EXTRA_RESUME = "resume"
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -270,8 +271,12 @@ class LockActivity : AppCompatActivity() {
}
private fun proceed() {
startActivity(Intent(this, HomeActivity::class.java))
finish()
if (intent.getBooleanExtra(EXTRA_RESUME, false)) {
finish()
} else {
startActivity(Intent(this, HomeActivity::class.java))
finish()
}
}
// ── Brute-force tracking ──────────────────────────────────────────────────
@@ -3,17 +3,18 @@ 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.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import sh.sar.basedbank.R
import sh.sar.basedbank.api.mib.MibAccount
import sh.sar.basedbank.databinding.ItemAccountBinding
import sh.sar.basedbank.databinding.ItemCardBinding
import sh.sar.basedbank.databinding.ItemDateHeaderBinding
import sh.sar.basedbank.util.BmlDashboardParser
import sh.sar.basedbank.util.MibAccountParser
class AccountsAdapter(
accounts: List<MibAccount>,
@@ -106,7 +107,11 @@ class AccountsAdapter(
fun bind(account: MibAccount) {
binding.tvAccountName.text = account.accountBriefName
binding.tvAccountNumber.text = account.accountNumber
binding.tvPillType.text = friendlyAccountType(account.accountTypeName)
val label = if (account.profileType.startsWith("BML"))
BmlDashboardParser.productLabel(account.accountTypeName)
else
MibAccountParser.productLabel(account.accountTypeName)
binding.tvPillType.text = label
binding.tvBalance.text = "${account.currencyName} ${account.availableBalance}"
binding.root.setOnClickListener { onAccountClick(account) }
binding.root.setOnLongClickListener {
@@ -119,15 +124,10 @@ class AccountsAdapter(
private inner class CardViewHolder(private val binding: ItemCardBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(account: MibAccount) {
val brand = cardBrand(account.accountTypeName)
binding.tvCardBrand.text = brand.label
binding.tvCardBrand.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 100f
setColor(Color.parseColor(brand.color))
}
binding.tvCardName.text = account.accountBriefName
binding.tvCardNumber.text = account.accountNumber
binding.ivCardBrand.setImageResource(cardBrandIcon(account.accountTypeName))
binding.tvCardName.text = account.accountBriefName
binding.tvCardNumber.text = account.accountNumber
binding.tvCardProduct.text = BmlDashboardParser.productLabel(account.accountTypeName)
binding.layoutCardBalance.visibility = View.VISIBLE
binding.tvCardBalance.text = "${account.currencyName} ${account.availableBalance}"
@@ -155,29 +155,12 @@ class AccountsAdapter(
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 fun cardBrand(productName: String): Brand = when {
private fun cardBrandIcon(productName: String): Int = when {
productName.contains("AMEX", ignoreCase = true) ||
productName.contains("AMERICAN EXPRESS", ignoreCase = true) -> Brand("AMEX", "#016FD0")
productName.contains("VISA", ignoreCase = true) -> Brand("VISA", "#1A1F71")
productName.contains("MASTERCARD", ignoreCase = true) -> Brand("MC", "#FF5F00")
else -> Brand("CARD", "#555555")
productName.contains("AMERICAN EXPRESS", ignoreCase = true) -> R.drawable.americanexpress
productName.contains("VISA", ignoreCase = true) -> R.drawable.visa
productName.contains("MASTERCARD", ignoreCase = true) -> R.drawable.mastercard
else -> R.drawable.ic_nav_card
}
}
}
@@ -14,7 +14,9 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import sh.sar.basedbank.util.ContactsCache
@@ -129,6 +131,22 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
}
}
private fun startLookupLoading() {
val spinner = CircularProgressDrawable(requireContext()).apply {
setStyle(CircularProgressDrawable.DEFAULT)
setColorSchemeColors(com.google.android.material.color.MaterialColors.getColor(
requireView(), com.google.android.material.R.attr.colorPrimary, Color.GRAY))
start()
}
binding.tilAccount.endIconDrawable = spinner
binding.tilAccount.isEnabled = false
}
private fun stopLookupLoading() {
binding.tilAccount.isEnabled = true
binding.tilAccount.endIconDrawable = ContextCompat.getDrawable(requireContext(), android.R.drawable.ic_menu_search)
}
private fun setupAccountSearch() {
binding.tilAccount.setEndIconOnClickListener { performLookup() }
}
@@ -167,7 +185,7 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
return
}
binding.tilAccount.isEnabled = false
startLookupLoading()
binding.tilDestination.isEnabled = false
binding.btnSave.isEnabled = false
@@ -175,7 +193,7 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
val result = withContext(Dispatchers.IO) {
if (dest.isBml) lookupForBml(input) else lookupForMib(dest, input)
}
binding.tilAccount.isEnabled = true
stopLookupLoading()
binding.tilDestination.isEnabled = true
if (result != null) {
showLookupResult(result, input)
@@ -341,6 +359,7 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
binding.tilAlias.error = null
binding.btnSave.isEnabled = false
binding.btnSave.text = "Saving..."
viewLifecycleOwner.lifecycleScope.launch {
val success = withContext(Dispatchers.IO) {
@@ -352,6 +371,7 @@ class AddContactSheetFragment : BottomSheetDialogFragment() {
dismiss()
} else {
binding.btnSave.isEnabled = true
binding.btnSave.text = "Save"
Toast.makeText(requireContext(), R.string.contact_save_failed, Toast.LENGTH_SHORT).show()
}
}
@@ -64,15 +64,22 @@ class HomeActivity : AppCompatActivity() {
private val warningRunnable = Runnable { showAutolockWarning() }
private var isLocked = false
private val autolockRunnable = Runnable {
countdownTimer?.cancel(); countdownTimer = null
warningDialog?.dismiss(); warningDialog = null
val securitySet = getSharedPreferences("prefs", MODE_PRIVATE)
.getString("security_method", null) != null
if (securitySet) {
startActivity(Intent(this, sh.sar.basedbank.LockActivity::class.java))
finish()
}
if (securitySet) lock()
}
private fun lock() {
isLocked = true
startActivity(
Intent(this, sh.sar.basedbank.LockActivity::class.java)
.putExtra(sh.sar.basedbank.LockActivity.EXTRA_RESUME, true)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -224,6 +231,13 @@ class HomeActivity : AppCompatActivity() {
}
}
fun setBottomNavVisible(visible: Boolean) {
val isBottom = getSharedPreferences("prefs", MODE_PRIVATE).getBoolean("bottom_nav", false)
if (isBottom) {
binding.bottomNavigation.visibility = if (visible) View.VISIBLE else View.GONE
}
}
fun setRefreshing(visible: Boolean) {
binding.refreshIndicator.visibility = if (visible) View.VISIBLE else View.GONE
}
@@ -237,6 +251,13 @@ class HomeActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// Returning from LockActivity — skip the elapsed check and reset state.
if (isLocked) {
isLocked = false
pauseTime = 0L
resetAutolockTimer()
return
}
// If we were away long enough to have hit the autolock timeout (e.g. while
// QrScannerActivity was in the foreground), lock immediately.
if (pauseTime > 0L) {
@@ -244,8 +265,7 @@ class HomeActivity : AppCompatActivity() {
val timeout = getSharedPreferences("prefs", MODE_PRIVATE).getLong("autolock_timeout", 60_000L)
val securitySet = getSharedPreferences("prefs", MODE_PRIVATE).getString("security_method", null) != null
if (timeout > 0L && elapsed >= timeout && securitySet) {
startActivity(Intent(this, sh.sar.basedbank.LockActivity::class.java))
finish()
lock()
return
}
}
@@ -316,8 +336,7 @@ class HomeActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_lock) {
startActivity(Intent(this, sh.sar.basedbank.LockActivity::class.java))
finish()
lock()
return true
}
return super.onOptionsItemSelected(item)
@@ -23,6 +23,7 @@ import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
@@ -159,6 +160,22 @@ class TransferFragment : Fragment() {
}
}
private fun startLookupLoading() {
val spinner = CircularProgressDrawable(requireContext()).apply {
setStyle(CircularProgressDrawable.DEFAULT)
setColorSchemeColors(com.google.android.material.color.MaterialColors.getColor(
requireView(), com.google.android.material.R.attr.colorPrimary, Color.GRAY))
start()
}
binding.tilTo.endIconDrawable = spinner
binding.tilTo.isEnabled = false
}
private fun stopLookupLoading() {
binding.tilTo.isEnabled = true
binding.tilTo.endIconDrawable = ContextCompat.getDrawable(requireContext(), android.R.drawable.ic_menu_search)
}
private fun setupFromDropdown() {
binding.btnClearFromInfo.setOnClickListener {
selectedAccount = null
@@ -313,7 +330,7 @@ class TransferFragment : Fragment() {
val isBmlSource = selectedAccount?.profileType?.startsWith("BML") == true
binding.tilTo.isEnabled = false
startLookupLoading()
viewLifecycleOwner.lifecycleScope.launch {
var errorMsg: String? = null
@@ -357,7 +374,7 @@ class TransferFragment : Fragment() {
}
}
}
binding.tilTo.isEnabled = true
stopLookupLoading()
if (info != null) {
val accounts = viewModel.accounts.value ?: emptyList()
val matchedAcc = accounts.firstOrNull { it.accountNumber == info.accountNumber }
@@ -392,7 +409,7 @@ class TransferFragment : Fragment() {
}
private fun lookupFahipayTarget(number: String) {
binding.tilTo.isEnabled = false
startLookupLoading()
viewLifecycleOwner.lifecycleScope.launch {
data class LookupResult(
val dhiraagu: DhiraaguClient.Result,
@@ -419,7 +436,7 @@ class TransferFragment : Fragment() {
LookupResult(d, o)
}
}
binding.tilTo.isEnabled = true
stopLookupLoading()
val dhiraaguName = result.dhiraagu.ownerName.takeIf { it.isNotBlank() }
@@ -586,6 +603,7 @@ class TransferFragment : Fragment() {
val doTransfer: () -> Unit = {
binding.btnTransfer.isEnabled = false
(activity as? HomeActivity)?.setRefreshing(true)
viewLifecycleOwner.lifecycleScope.launch {
val (ok, msg, receipt) = withContext(Dispatchers.IO) {
if (!isSrcBml) {
@@ -595,6 +613,7 @@ class TransferFragment : Fragment() {
}
}
binding.btnTransfer.isEnabled = true
(activity as? HomeActivity)?.setRefreshing(false)
if (ok && receipt != null) {
clearForm()
val activity = requireActivity() as HomeActivity
@@ -313,6 +313,12 @@ class TransferReceiptFragment : Fragment() {
override fun onResume() {
super.onResume()
requireActivity().title = "Receipt"
(activity as? HomeActivity)?.setBottomNavVisible(false)
}
override fun onPause() {
super.onPause()
(activity as? HomeActivity)?.setBottomNavVisible(true)
}
override fun onDestroyView() {
@@ -0,0 +1,28 @@
package sh.sar.basedbank.util
object BmlDashboardParser {
/**
* Returns a display-ready product label for a BML dashboard account or card.
* Known BML product names are mapped to short friendly labels.
* Everything else is title-cased (first letter of each word capitalised).
*/
fun productLabel(raw: String): String {
val u = raw.trim().uppercase()
return when {
u == "SAVINGS 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 -> toTitleCase(raw)
}
}
fun toTitleCase(input: String): String =
input.trim().lowercase().split(" ").joinToString(" ") { word ->
word.replaceFirstChar { it.uppercaseChar() }
}
}
@@ -0,0 +1,18 @@
package sh.sar.basedbank.util
object MibAccountParser {
/**
* Returns a display-ready product label for a MIB (Faisanet) account type name.
* Known MIB accountTypeName values are mapped to short friendly labels.
* Everything else is returned trimmed as-is.
*/
fun productLabel(raw: String): String {
val u = raw.trim().uppercase()
return when {
u == "SAVING ACCOUNT" -> "Savings"
u == "CURRENT ACCOUNT" -> "Current"
else -> raw.trim()
}
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#016FD0"
android:pathData="M16.015 14.378c0-.32-.135-.496-.344-.622-.21-.12-.464-.135-.81-.135h-1.543v2.82h.675v-1.027h.72c.24 0 .39.024.478.125.12.13.104.38.104.55v.35h.66v-.555c-.002-.25-.017-.376-.108-.516-.06-.08-.18-.18-.33-.234l.02-.008c.18-.072.48-.297.48-.747zm-.87.407l-.028-.002c-.09.053-.195.058-.33.058h-.81v-.63h.824c.12 0 .24 0 .33.05.098.048.156.147.15.255 0 .12-.045.215-.134.27zM20.297 15.837H19v.6h1.304c.676 0 1.05-.278 1.05-.884 0-.28-.066-.448-.187-.582-.153-.133-.392-.193-.73-.207l-.376-.015c-.104 0-.18 0-.255-.03-.09-.03-.15-.105-.15-.21 0-.09.017-.166.09-.21.083-.046.177-.066.272-.06h1.23v-.602h-1.35c-.704 0-.958.437-.958.84 0 .9.776.855 1.407.87.104 0 .18.015.225.06.046.03.082.106.082.18 0 .077-.035.15-.08.18-.06.053-.15.07-.277.07zM0 0v10.096L.81 8.22h1.75l.225.464V8.22h2.043l.45 1.02.437-1.013h6.502c.295 0 .56.057.756.236v-.23h1.787v.23c.307-.17.686-.23 1.12-.23h2.606l.24.466v-.466h1.918l.254.465v-.466h1.858v3.948H20.87l-.36-.6v.585h-2.353l-.256-.63h-.583l-.27.614h-1.213c-.48 0-.84-.104-1.08-.24v.24h-2.89v-.884c0-.12-.03-.12-.105-.135h-.105v1.036H6.067v-.48l-.21.48H4.69l-.202-.48v.465H2.235l-.256-.624H1.4l-.256.624H0V24h23.786v-7.108c-.27.135-.613.18-.973.18H21.09v-.255c-.21.165-.57.255-.914.255H14.71v-.9c0-.12-.018-.12-.12-.12h-.075v1.022h-1.8v-1.066c-.298.136-.643.15-.928.136h-.214v.915h-2.18l-.54-.617-.57.6H4.742v-3.93h3.61l.518.602.554-.6h2.412c.28 0 .74.03.942.225v-.24h2.177c.202 0 .644.045.903.225v-.24h3.265v.24c.163-.164.508-.24.803-.24h1.89v.24c.194-.15.464-.24.84-.24h1.176V0H0zM21.156 14.955c.004.005.006.012.01.016.01.01.024.01.032.02l-.042-.035zM23.828 13.082h.065v.555h-.065zM23.865 15.03v-.005c-.03-.025-.046-.048-.075-.07-.15-.153-.39-.215-.764-.225l-.36-.012c-.12 0-.194-.007-.27-.03-.09-.03-.15-.105-.15-.21 0-.09.03-.16.09-.204.076-.045.15-.05.27-.05h1.223v-.588h-1.283c-.69 0-.96.437-.96.84 0 .9.78.855 1.41.87.104 0 .18.015.224.06.046.03.076.106.076.18 0 .07-.034.138-.09.18-.045.056-.136.07-.27.07h-1.288v.605h1.287c.42 0 .734-.118.9-.36h.03c.09-.134.135-.3.135-.523 0-.24-.045-.39-.135-.526zM18.597 14.208v-.583h-2.235V16.458h2.235v-.585h-1.57v-.57h1.533v-.584h-1.532v-.51M13.51 8.787h.685V11.6h-.684zM13.126 9.543l-.007.006c0-.314-.13-.5-.34-.624-.217-.125-.47-.135-.81-.135H10.43v2.82h.674v-1.034h.72c.24 0 .39.03.487.12.122.136.107.378.107.548v.354h.677v-.553c0-.25-.016-.375-.11-.516-.09-.107-.202-.19-.33-.237.172-.07.472-.3.472-.75zm-.855.396h-.015c-.09.054-.195.056-.33.056H11.1v-.623h.825c.12 0 .24.004.33.05.09.04.15.128.15.25s-.047.22-.134.266zM15.92 9.373h.632v-.6h-.644c-.464 0-.804.105-1.02.33-.286.3-.362.69-.362 1.11 0 .512.123.833.36 1.074.232.238.645.31.97.31h.78l.255-.627h1.39l.262.627h1.36v-2.11l1.272 2.11h.95l.002.002V8.786h-.684v1.963l-1.18-1.96h-1.02V11.4L18.11 8.744h-1.004l-.943 2.22h-.3c-.177 0-.362-.03-.468-.134-.125-.15-.186-.36-.186-.662 0-.285.08-.51.194-.63.133-.135.272-.165.516-.165zm1.668-.108l.464 1.118v.002h-.93l.466-1.12zM2.38 10.97l.254.628H4V9.393l.972 2.205h.584l.973-2.202.015 2.202h.69v-2.81H6.118l-.807 1.904-.876-1.905H3.343v2.663L2.205 8.787h-.997L.01 11.597h.72l.26-.626h1.39zm-.688-1.705l.46 1.118-.003.002h-.915l.457-1.12zM11.856 13.62H9.714l-.85.923-.825-.922H5.346v2.82H8l.855-.932.824.93h1.302v-.94h.838c.6 0 1.17-.164 1.17-.945l-.006-.003c0-.78-.598-.93-1.128-.93zM7.67 15.853l-.014-.002H6.02v-.557h1.47v-.574H6.02v-.51H7.7l.733.82-.764.824zm2.642.33l-1.03-1.147 1.03-1.108v2.253zm1.553-1.258h-.885v-.717h.885c.24 0 .42.098.42.344 0 .243-.15.372-.42.372zM9.967 9.373v-.586H7.73V11.6h2.237v-.58H8.4v-.564h1.527V9.88H8.4v-.507" />
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF5F00"
android:pathData="M11.343 18.031c.058.049.12.098.181.146-1.177.783-2.59 1.238-4.107 1.238C3.32 19.416 0 16.096 0 12c0-4.095 3.32-7.416 7.416-7.416 1.518 0 2.931.456 4.105 1.238-.06.051-.12.098-.165.15C9.6 7.489 8.595 9.688 8.595 12c0 2.311 1.001 4.51 2.748 6.031zm5.241-13.447c-1.52 0-2.931.456-4.105 1.238.06.051.12.098.165.15C14.4 7.489 15.405 9.688 15.405 12c0 2.31-1.001 4.507-2.748 6.031-.058.049-.12.098-.181.146 1.177.783 2.588 1.238 4.107 1.238C20.68 19.416 24 16.096 24 12c0-4.094-3.32-7.416-7.416-7.416zM12 6.174c-.096.075-.189.15-.28.231C10.156 7.764 9.169 9.765 9.169 12c0 2.236.987 4.236 2.551 5.595.09.08.185.158.28.232.096-.074.189-.152.28-.232 1.563-1.359 2.551-3.359 2.551-5.595 0-2.235-.987-4.236-2.551-5.595-.09-.08-.184-.156-.28-.231z" />
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#1A1F71"
android:pathData="M9.112 8.262L5.97 15.758H3.92L2.374 9.775c-0.094,-0.368,-0.175,-0.503,-0.461,-0.658C1.447 8.864 0.677 8.627 0 8.479l0.046,-0.217h3.3a0.904,0.904,0,0,1,0.894,0.764l0.817 4.338 2.018,-5.102zM17.145 13.311c0.008,-1.979,-2.736,-2.088,-2.717,-2.972 0.006,-0.269 0.262,-0.555 0.822,-0.628a3.66,3.66,0,0,1,1.913,0.336l0.34,-1.59a5.207,5.207,0,0,0,-1.814,-0.333c-1.917 0,-3.266 1.02,-3.278 2.479,-0.012 1.079 0.963 1.68 1.698 2.04 0.756 0.367 1.01 0.603 1.006 0.931,-0.005 0.504,-0.602 0.725,-1.16 0.734,-0.975 0.015,-1.54,-0.263,-1.992,-0.473l-0.351 1.642c0.453 0.208 1.289 0.39 2.156 0.398 2.037 0 3.37,-1.006 3.377,-2.564zM22.206 15.758H24l-1.565,-7.496h-1.656a0.883,0.883,0,0,0,-0.826,0.55l-2.909 6.946h2.036l0.405,-1.12h2.488zM20.043 13.102l1.02,-2.815 0.588 2.815zM11.883 8.262l-1.603 7.496H8.34l1.605,-7.496z" />
</vector>
@@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFF">
android:background="?attr/colorSurface">
<!-- ══════════════════════════════════════════════════════════════════════ -->
<!-- Renderable receipt card -->
@@ -206,12 +206,6 @@
</LinearLayout>
<!-- Pushes buttons to bottom of screen -->
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- ══════════════════════════════════════════════════════════════════════ -->
<!-- Action buttons — outside renderable area -->
<!-- ══════════════════════════════════════════════════════════════════════ -->
@@ -219,7 +213,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#FFFFFF"
android:background="?attr/colorSurface"
android:paddingHorizontal="12dp"
android:paddingTop="8dp"
android:paddingBottom="12dp">
+15 -11
View File
@@ -14,18 +14,14 @@
android:gravity="center_vertical"
android:foreground="?attr/selectableItemBackground">
<!-- Brand chip -->
<TextView
android:id="@+id/tvCardBrand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:paddingVertical="6dp"
<!-- Brand logo -->
<ImageView
android:id="@+id/ivCardBrand"
android:layout_width="48dp"
android:layout_height="32dp"
android:layout_marginEnd="16dp"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="@android:color/white"
android:fontFamily="monospace"
android:background="@drawable/chip_background" />
android:scaleType="fitCenter"
android:contentDescription="Card brand" />
<!-- Card name + number -->
<LinearLayout
@@ -50,6 +46,14 @@
android:layout_marginTop="2dp"
android:fontFamily="monospace" />
<TextView
android:id="@+id/tvCardProduct"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginTop="2dp" />
</LinearLayout>
<!-- Status pill + balance (prepaid only) -->
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
agp = "8.9.1"
agp = "8.7.3"
kotlin = "2.1.21"
coreKtx = "1.10.1"
junit = "4.13.2"