forked from shihaam/thijooree
add support for fahipay images
This commit is contained in:
@@ -237,7 +237,8 @@ class FahipayLoginFlow {
|
|||||||
reference = e.optString("transaction").takeIf { it.isNotBlank() },
|
reference = e.optString("transaction").takeIf { it.isNotBlank() },
|
||||||
accountNumber = accountNumber,
|
accountNumber = accountNumber,
|
||||||
accountDisplayName = accountDisplayName,
|
accountDisplayName = accountDisplayName,
|
||||||
source = "FAHIPAY"
|
source = "FAHIPAY",
|
||||||
|
iconUrl = e.optString("icon").takeIf { it.isNotBlank() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Pair(list, total)
|
Pair(list, total)
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ data class Transaction(
|
|||||||
val reference: String?,
|
val reference: String?,
|
||||||
val accountNumber: String,
|
val accountNumber: String,
|
||||||
val accountDisplayName: 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(
|
data class MibFinanceDeal(
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ object TransactionCache {
|
|||||||
put("accountNumber", t.accountNumber)
|
put("accountNumber", t.accountNumber)
|
||||||
put("accountDisplayName", t.accountDisplayName)
|
put("accountDisplayName", t.accountDisplayName)
|
||||||
put("source", t.source)
|
put("source", t.source)
|
||||||
|
if (t.iconUrl != null) put("iconUrl", t.iconUrl)
|
||||||
})
|
})
|
||||||
File(context.cacheDir, "tx_$key.json").writeText(CacheEncryption.encrypt(arr.toString()))
|
File(context.cacheDir, "tx_$key.json").writeText(CacheEncryption.encrypt(arr.toString()))
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {}
|
||||||
@@ -46,7 +47,8 @@ object TransactionCache {
|
|||||||
reference = o.optString("reference").takeIf { it.isNotBlank() },
|
reference = o.optString("reference").takeIf { it.isNotBlank() },
|
||||||
accountNumber = o.optString("accountNumber"),
|
accountNumber = o.optString("accountNumber"),
|
||||||
accountDisplayName = o.optString("accountDisplayName"),
|
accountDisplayName = o.optString("accountDisplayName"),
|
||||||
source = o.optString("source")
|
source = o.optString("source"),
|
||||||
|
iconUrl = o.optString("iconUrl").takeIf { it.isNotBlank() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (_: Exception) { emptyList() }
|
} catch (_: Exception) { emptyList() }
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ class AccountHistoryAdapter(
|
|||||||
private val displayItems = mutableListOf<Item>()
|
private val displayItems = mutableListOf<Item>()
|
||||||
private var lastInsertedDateKey = ""
|
private var lastInsertedDateKey = ""
|
||||||
private val imageCache = mutableMapOf<String, Bitmap>()
|
private val imageCache = mutableMapOf<String, Bitmap>()
|
||||||
|
private val iconUrlCache = mutableMapOf<String, Bitmap>()
|
||||||
var onImageNeeded: ((counterpartyName: String) -> Unit)? = null
|
var onImageNeeded: ((counterpartyName: String) -> Unit)? = null
|
||||||
|
var onIconUrlNeeded: ((url: String) -> Unit)? = null
|
||||||
|
|
||||||
fun updateImage(counterpartyName: String, bitmap: Bitmap) {
|
fun updateImage(counterpartyName: String, bitmap: Bitmap) {
|
||||||
imageCache[counterpartyName] = 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
|
private var _showLoadingFooter = false
|
||||||
var showLoadingFooter: Boolean
|
var showLoadingFooter: Boolean
|
||||||
get() = _showLoadingFooter
|
get() = _showLoadingFooter
|
||||||
@@ -182,7 +192,10 @@ class AccountHistoryAdapter(
|
|||||||
val name = trx.counterpartyName ?: trx.description
|
val name = trx.counterpartyName ?: trx.description
|
||||||
val initial = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?"
|
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) {
|
if (bitmap != null) {
|
||||||
val rd = RoundedBitmapDrawableFactory.create(b.root.resources, bitmap)
|
val rd = RoundedBitmapDrawableFactory.create(b.root.resources, bitmap)
|
||||||
rd.isCircular = true
|
rd.isCircular = true
|
||||||
@@ -196,7 +209,8 @@ class AccountHistoryAdapter(
|
|||||||
b.fvAvatar.background = circle
|
b.fvAvatar.background = circle
|
||||||
b.tvInitial.visibility = View.VISIBLE
|
b.tvInitial.visibility = View.VISIBLE
|
||||||
b.tvInitial.text = initial
|
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
|
b.tvDescription.text = trx.description
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package sh.sar.basedbank.ui.home
|
package sh.sar.basedbank.ui.home
|
||||||
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
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.api.mib.TransactionCache
|
||||||
import sh.sar.basedbank.databinding.FragmentAccountHistoryBinding
|
import sh.sar.basedbank.databinding.FragmentAccountHistoryBinding
|
||||||
import sh.sar.basedbank.util.ContactImageCache
|
import sh.sar.basedbank.util.ContactImageCache
|
||||||
|
import sh.sar.basedbank.util.MerchantIconCache
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -44,6 +47,7 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
private var searchQuery = ""
|
private var searchQuery = ""
|
||||||
private var firstPageDone = false
|
private var firstPageDone = false
|
||||||
private val pendingImageNames = mutableSetOf<String>()
|
private val pendingImageNames = mutableSetOf<String>()
|
||||||
|
private val pendingIconUrls = mutableSetOf<String>()
|
||||||
|
|
||||||
// Pagination state
|
// Pagination state
|
||||||
private var mibNextStart = 1
|
private var mibNextStart = 1
|
||||||
@@ -77,6 +81,7 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
|
|
||||||
adapter = AccountHistoryAdapter(account)
|
adapter = AccountHistoryAdapter(account)
|
||||||
adapter.onImageNeeded = { name -> loadContactImage(name) }
|
adapter.onImageNeeded = { name -> loadContactImage(name) }
|
||||||
|
adapter.onIconUrlNeeded = { url -> loadMerchantIcon(url) }
|
||||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
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() {
|
override fun onDestroyView() {
|
||||||
(activity as? HomeActivity)?.setRefreshing(false)
|
(activity as? HomeActivity)?.setRefreshing(false)
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user