add support for fahipay images
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 3s

This commit is contained in:
2026-05-16 21:42:04 +05:00
parent 7864655a82
commit d4104e2ed2
6 changed files with 75 additions and 5 deletions

View File

@@ -237,7 +237,8 @@ class FahipayLoginFlow {
reference = e.optString("transaction").takeIf { it.isNotBlank() },
accountNumber = accountNumber,
accountDisplayName = accountDisplayName,
source = "FAHIPAY"
source = "FAHIPAY",
iconUrl = e.optString("icon").takeIf { it.isNotBlank() }
)
}
Pair(list, total)

View File

@@ -82,7 +82,8 @@ data class Transaction(
val reference: String?,
val accountNumber: String,
val accountDisplayName: String,
val source: String // "MIB", "BML", "BML_CARD"
val source: String, // "MIB", "BML", "BML_CARD", "FAHIPAY"
val iconUrl: String? = null // merchant icon URL (Fahipay only)
)
data class MibFinanceDeal(

View File

@@ -22,6 +22,7 @@ object TransactionCache {
put("accountNumber", t.accountNumber)
put("accountDisplayName", t.accountDisplayName)
put("source", t.source)
if (t.iconUrl != null) put("iconUrl", t.iconUrl)
})
File(context.cacheDir, "tx_$key.json").writeText(CacheEncryption.encrypt(arr.toString()))
} catch (_: Exception) {}
@@ -46,7 +47,8 @@ object TransactionCache {
reference = o.optString("reference").takeIf { it.isNotBlank() },
accountNumber = o.optString("accountNumber"),
accountDisplayName = o.optString("accountDisplayName"),
source = o.optString("source")
source = o.optString("source"),
iconUrl = o.optString("iconUrl").takeIf { it.isNotBlank() }
)
}
} catch (_: Exception) { emptyList() }

View File

@@ -31,7 +31,9 @@ class AccountHistoryAdapter(
private val displayItems = mutableListOf<Item>()
private var lastInsertedDateKey = ""
private val imageCache = mutableMapOf<String, Bitmap>()
private val iconUrlCache = mutableMapOf<String, Bitmap>()
var onImageNeeded: ((counterpartyName: String) -> Unit)? = null
var onIconUrlNeeded: ((url: String) -> Unit)? = null
fun updateImage(counterpartyName: String, bitmap: Bitmap) {
imageCache[counterpartyName] = bitmap
@@ -41,6 +43,14 @@ class AccountHistoryAdapter(
}
}
fun updateIconUrl(url: String, bitmap: Bitmap) {
iconUrlCache[url] = bitmap
displayItems.forEachIndexed { i, item ->
if (item is Item.Trx && item.transaction.iconUrl == url)
notifyItemChanged(i + 1) // +1 for account header at position 0
}
}
private var _showLoadingFooter = false
var showLoadingFooter: Boolean
get() = _showLoadingFooter
@@ -182,7 +192,10 @@ class AccountHistoryAdapter(
val name = trx.counterpartyName ?: trx.description
val initial = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?"
val bitmap = trx.counterpartyName?.let { imageCache[it] }
val iconBitmap = trx.iconUrl?.let { iconUrlCache[it] }
val contactBitmap = if (iconBitmap == null) trx.counterpartyName?.let { imageCache[it] } else null
val bitmap = iconBitmap ?: contactBitmap
if (bitmap != null) {
val rd = RoundedBitmapDrawableFactory.create(b.root.resources, bitmap)
rd.isCircular = true
@@ -196,7 +209,8 @@ class AccountHistoryAdapter(
b.fvAvatar.background = circle
b.tvInitial.visibility = View.VISIBLE
b.tvInitial.text = initial
if (trx.counterpartyName != null) onImageNeeded?.invoke(trx.counterpartyName)
if (trx.iconUrl != null) onIconUrlNeeded?.invoke(trx.iconUrl)
else if (trx.counterpartyName != null) onImageNeeded?.invoke(trx.counterpartyName)
}
b.tvDescription.text = trx.description

View File

@@ -1,6 +1,8 @@
package sh.sar.basedbank.ui.home
import android.graphics.BitmapFactory
import okhttp3.OkHttpClient
import okhttp3.Request
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@@ -27,6 +29,7 @@ import sh.sar.basedbank.api.mib.Transaction
import sh.sar.basedbank.api.mib.TransactionCache
import sh.sar.basedbank.databinding.FragmentAccountHistoryBinding
import sh.sar.basedbank.util.ContactImageCache
import sh.sar.basedbank.util.MerchantIconCache
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@@ -44,6 +47,7 @@ class AccountHistoryFragment : Fragment() {
private var searchQuery = ""
private var firstPageDone = false
private val pendingImageNames = mutableSetOf<String>()
private val pendingIconUrls = mutableSetOf<String>()
// Pagination state
private var mibNextStart = 1
@@ -77,6 +81,7 @@ class AccountHistoryFragment : Fragment() {
adapter = AccountHistoryAdapter(account)
adapter.onImageNeeded = { name -> loadContactImage(name) }
adapter.onIconUrlNeeded = { url -> loadMerchantIcon(url) }
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = adapter
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
@@ -266,6 +271,29 @@ class AccountHistoryFragment : Fragment() {
}
}
private fun loadMerchantIcon(url: String) {
if (!pendingIconUrls.add(url)) return
val cached = MerchantIconCache.load(requireContext(), url)
if (cached != null) {
binding.recyclerView.post { adapter.updateIconUrl(url, cached) }
return
}
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
try {
val client = OkHttpClient()
val response = client.newCall(Request.Builder().url(url).build()).execute()
val bytes = response.body?.bytes() ?: return@launch
response.close()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
?: return@launch
MerchantIconCache.save(requireContext(), url, bitmap)
withContext(Dispatchers.Main) { adapter.updateIconUrl(url, bitmap) }
} catch (_: Exception) {
pendingIconUrls.remove(url)
}
}
}
override fun onDestroyView() {
(activity as? HomeActivity)?.setRefreshing(false)
super.onDestroyView()

View File

@@ -0,0 +1,24 @@
package sh.sar.basedbank.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.File
object MerchantIconCache {
private fun file(context: Context, url: String) =
File(context.cacheDir, "micon_${url.replace(Regex("[^A-Za-z0-9]"), "_")}.png")
fun save(context: Context, url: String, bitmap: Bitmap) {
try {
file(context, url).outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, it)
}
} catch (_: Exception) {}
}
fun load(context: Context, url: String): Bitmap? = try {
BitmapFactory.decodeFile(file(context, url).absolutePath)
} catch (_: Exception) { null }
}