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 642d7bc..8fc7ad3 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 @@ -3,11 +3,13 @@ package sh.sar.basedbank.ui.home import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.graphics.Bitmap 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.models.BankAccount import sh.sar.basedbank.databinding.ItemAccountBinding import sh.sar.basedbank.databinding.ItemCardBinding @@ -17,7 +19,9 @@ import sh.sar.basedbank.util.AccountListParser class AccountsAdapter( accounts: List, - private val onAccountClick: (BankAccount) -> Unit = {} + private val onAccountClick: (BankAccount) -> Unit = {}, + /** Optional loader for MIB per-profile images: (hash, onLoaded) */ + private val profileImageLoader: ((String, (Bitmap) -> Unit) -> Unit)? = null ) : RecyclerView.Adapter() { var onTransferClick: ((BankAccount) -> Unit)? = null @@ -112,6 +116,8 @@ class AccountsAdapter( private inner class AccountViewHolder(private val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root) { + private var boundHash: String? = null + fun bind(account: BankAccount, display: AccountListDisplay) { binding.tvAccountName.text = display.name binding.tvAccountNumber.text = display.number @@ -123,6 +129,23 @@ class AccountsAdapter( copyToClipboard(it.context, display.number) true } + + val staticLogo = when (account.bank) { + "BML" -> R.drawable.bml_logo_vector + "FAHIPAY" -> R.drawable.fahipay_logo + "MIB" -> R.drawable.mib_logo + else -> null + } + if (staticLogo != null) binding.ivBankLogo.setImageResource(staticLogo) + else binding.ivBankLogo.setImageDrawable(null) + + val hash = account.profileImageHash + boundHash = hash + if (account.bank == "MIB" && hash != null && profileImageLoader != null) { + profileImageLoader.invoke(hash) { bitmap -> + if (boundHash == hash) binding.ivBankLogo.setImageBitmap(bitmap) + } + } } } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt index 7f491d2..07c09cd 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsFragment.kt @@ -1,6 +1,9 @@ package sh.sar.basedbank.ui.home +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.os.Bundle +import android.util.Base64 import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -8,7 +11,12 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.R import sh.sar.basedbank.databinding.FragmentAccountsBinding @@ -18,6 +26,7 @@ class AccountsFragment : Fragment() { private val binding get() = _binding!! private val viewModel: HomeViewModel by activityViewModels() private lateinit var adapter: AccountsAdapter + private val profileImageCache = mutableMapOf() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentAccountsBinding.inflate(inflater, container, false) @@ -25,9 +34,30 @@ class AccountsFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - adapter = AccountsAdapter(emptyList()) { account -> - (activity as? HomeActivity)?.showWithBackStack(AccountHistoryFragment.newInstance(account)) - } + val app = requireActivity().application as BasedBankApp + adapter = AccountsAdapter( + accounts = emptyList(), + onAccountClick = { account -> + (activity as? HomeActivity)?.showWithBackStack(AccountHistoryFragment.newInstance(account)) + }, + profileImageLoader = { hash, onLoaded -> + profileImageCache[hash]?.let { onLoaded(it); return@AccountsAdapter } + viewLifecycleOwner.lifecycleScope.launch { + val bitmap = withContext(Dispatchers.IO) { + try { + val session = app.anyMibSession() ?: return@withContext null + val b64 = app.anyMibFlow()?.fetchProfileImage(session, hash) ?: return@withContext null + val bytes = Base64.decode(b64, Base64.DEFAULT) + BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + } catch (_: Exception) { null } + } + if (bitmap != null) { + profileImageCache[hash] = bitmap + onLoaded(bitmap) + } + } + } + ) adapter.onTransferClick = { account -> (activity as? HomeActivity)?.navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFrom(account)) } 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 bd03b0a..8b8b21b 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 @@ -93,6 +93,7 @@ class TransferFragment : Fragment() { // BML QR merchant payment mode (set when navigated from a card QR scan) private var bmlQrInfo: BmlQrPayInfo? = null private var bmlGatewayQr = false // true for pay.bml.com.mv QRs (requires pre-initiate step) + private val dropdownProfileImageCache = mutableMapOf() // BML business profile OTP flow state private enum class BmlOtpState { NONE, SELECTING_CHANNEL, AWAITING_OTP } @@ -1629,11 +1630,47 @@ class TransferFragment : Fragment() { b.tvDropdownBalance.text = if (hide && balance.isNotBlank()) maskAmount(balance) else balance b.root.alpha = if (inactive) 0.4f else 1f val networkIcon = BmlCardParser.cardNetworkIcon(acc) - if (networkIcon != null) { - b.ivDropdownCardLogo.setImageResource(networkIcon) - b.ivDropdownCardLogo.visibility = View.VISIBLE - } else { - b.ivDropdownCardLogo.visibility = View.GONE + when { + networkIcon != null -> { + b.ivDropdownCardLogo.setImageResource(networkIcon) + b.ivDropdownCardLogo.visibility = View.VISIBLE + } + acc.bank == "BML" -> { + b.ivDropdownCardLogo.setImageResource(R.drawable.bml_logo_vector) + b.ivDropdownCardLogo.visibility = View.VISIBLE + } + acc.bank == "FAHIPAY" -> { + b.ivDropdownCardLogo.setImageResource(R.drawable.fahipay_logo) + b.ivDropdownCardLogo.visibility = View.VISIBLE + } + acc.bank == "MIB" -> { + val hash = acc.profileImageHash + val cached = hash?.let { dropdownProfileImageCache[it] } + if (cached != null) { + b.ivDropdownCardLogo.setImageBitmap(cached) + } else { + b.ivDropdownCardLogo.setImageResource(R.drawable.mib_logo) + if (hash != null) { + val app = requireActivity().application as BasedBankApp + viewLifecycleOwner.lifecycleScope.launch { + val bitmap = withContext(Dispatchers.IO) { + try { + val sess = app.anyMibSession() ?: return@withContext null + val b64 = app.anyMibFlow()?.fetchProfileImage(sess, hash) ?: return@withContext null + val bytes = android.util.Base64.decode(b64, android.util.Base64.DEFAULT) + android.graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + } catch (_: Exception) { null } + } + if (bitmap != null) { + dropdownProfileImageCache[hash] = bitmap + accountDropdownAdapter?.notifyDataSetChanged() + } + } + } + } + b.ivDropdownCardLogo.visibility = View.VISIBLE + } + else -> b.ivDropdownCardLogo.visibility = View.GONE } b.root } diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml index 21541e4..99cdbc8 100644 --- a/app/src/main/res/layout/item_account.xml +++ b/app/src/main/res/layout/item_account.xml @@ -14,6 +14,16 @@ android:gravity="center_vertical" android:foreground="?attr/selectableItemBackground"> + + + - + android:visibility="gone" + app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" + xmlns:app="http://schemas.android.com/apk/res-auto" />