forked from shihaam/thijooree
add support to View details of blocked balance #33
This commit is contained in:
@@ -153,6 +153,46 @@ class BmlHistoryClient {
|
|||||||
} catch (_: Exception) { emptyList() }
|
} catch (_: Exception) { emptyList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fetchPendingHistory(
|
||||||
|
session: BmlSession,
|
||||||
|
accountId: String,
|
||||||
|
accountDisplayName: String,
|
||||||
|
accountNumber: String
|
||||||
|
): List<BankTransaction> {
|
||||||
|
val resp = client.newCall(
|
||||||
|
Request.Builder().url("$BML_BASE_URL/api/mobile/history/pending/$accountId")
|
||||||
|
.header("Authorization", "Bearer ${session.accessToken}")
|
||||||
|
.header("User-Agent", BML_USER_AGENT)
|
||||||
|
.header("x-app-version", BML_APP_VERSION)
|
||||||
|
.build()
|
||||||
|
).execute()
|
||||||
|
val code = resp.code
|
||||||
|
val json = resp.body?.string() ?: return emptyList()
|
||||||
|
resp.close()
|
||||||
|
if (code == 401 || code == 419) throw AuthExpiredException()
|
||||||
|
if (code in 500..599) throw BankServerException("BML")
|
||||||
|
return try {
|
||||||
|
val root = JSONObject(json)
|
||||||
|
if (!root.optBoolean("success")) return emptyList()
|
||||||
|
val payload = root.optJSONArray("payload") ?: return emptyList()
|
||||||
|
(0 until payload.length()).map { i ->
|
||||||
|
val item = payload.getJSONObject(i)
|
||||||
|
BankTransaction(
|
||||||
|
id = item.optString("LockedID"),
|
||||||
|
date = item.optString("FromDate"),
|
||||||
|
description = "Pending",
|
||||||
|
amount = -item.optDouble("LockedAmount", 0.0),
|
||||||
|
currency = "MVR",
|
||||||
|
counterpartyName = item.optString("Description").trim().takeIf { it.isNotBlank() },
|
||||||
|
reference = null,
|
||||||
|
accountNumber = accountNumber,
|
||||||
|
accountDisplayName = accountDisplayName,
|
||||||
|
source = "BML"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) { emptyList() }
|
||||||
|
}
|
||||||
|
|
||||||
// "12-05-2026 041675" → first 4 digits of time part as HH:mm
|
// "12-05-2026 041675" → first 4 digits of time part as HH:mm
|
||||||
private fun parsePurchaseNarrative1(narrative1: String): String? {
|
private fun parsePurchaseNarrative1(narrative1: String): String? {
|
||||||
return try {
|
return try {
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ class AccountHistoryAdapter(
|
|||||||
|
|
||||||
private sealed class Item {
|
private sealed class Item {
|
||||||
data class DateHeader(val label: String) : Item()
|
data class DateHeader(val label: String) : Item()
|
||||||
data class Trx(val transaction: BankTransaction) : Item()
|
data class Trx(val transaction: BankTransaction, val showDate: Boolean = false) : Item()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val pendingItems = mutableListOf<Item>()
|
||||||
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>()
|
||||||
@@ -48,9 +49,11 @@ class AccountHistoryAdapter(
|
|||||||
if (hideAmounts == hide) return
|
if (hideAmounts == hide) return
|
||||||
hideAmounts = hide
|
hideAmounts = hide
|
||||||
notifyItemChanged(0) // refresh header card
|
notifyItemChanged(0) // refresh header card
|
||||||
// refresh all transaction rows
|
for (i in pendingItems.indices) {
|
||||||
|
if (pendingItems[i] is Item.Trx) notifyItemChanged(i + 1)
|
||||||
|
}
|
||||||
for (i in displayItems.indices) {
|
for (i in displayItems.indices) {
|
||||||
if (displayItems[i] is Item.Trx) notifyItemChanged(i + 1)
|
if (displayItems[i] is Item.Trx) notifyItemChanged(1 + pendingItems.size + i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ class AccountHistoryAdapter(
|
|||||||
imageCache[counterpartyName] = bitmap
|
imageCache[counterpartyName] = bitmap
|
||||||
displayItems.forEachIndexed { i, item ->
|
displayItems.forEachIndexed { i, item ->
|
||||||
if (item is Item.Trx && item.transaction.counterpartyName == counterpartyName)
|
if (item is Item.Trx && item.transaction.counterpartyName == counterpartyName)
|
||||||
notifyItemChanged(i + 1) // +1 for account header at position 0
|
notifyItemChanged(1 + pendingItems.size + i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,10 +69,19 @@ class AccountHistoryAdapter(
|
|||||||
iconUrlCache[url] = bitmap
|
iconUrlCache[url] = bitmap
|
||||||
displayItems.forEachIndexed { i, item ->
|
displayItems.forEachIndexed { i, item ->
|
||||||
if (item is Item.Trx && item.transaction.iconUrl == url)
|
if (item is Item.Trx && item.transaction.iconUrl == url)
|
||||||
notifyItemChanged(i + 1) // +1 for account header at position 0
|
notifyItemChanged(1 + pendingItems.size + i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setPendingTransactions(transactions: List<BankTransaction>) {
|
||||||
|
pendingItems.clear()
|
||||||
|
if (transactions.isNotEmpty()) {
|
||||||
|
pendingItems.add(Item.DateHeader("Pending"))
|
||||||
|
for (trx in transactions) pendingItems.add(Item.Trx(trx, showDate = true))
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
private var _showLoadingFooter = false
|
private var _showLoadingFooter = false
|
||||||
var showLoadingFooter: Boolean
|
var showLoadingFooter: Boolean
|
||||||
get() = _showLoadingFooter
|
get() = _showLoadingFooter
|
||||||
@@ -127,18 +139,24 @@ class AccountHistoryAdapter(
|
|||||||
displayItems.add(Item.Trx(trx))
|
displayItems.add(Item.Trx(trx))
|
||||||
}
|
}
|
||||||
val added = displayItems.size - oldCount
|
val added = displayItems.size - oldCount
|
||||||
if (added > 0) notifyItemRangeInserted(1 + oldCount, added) // +1 for account header
|
if (added > 0) notifyItemRangeInserted(1 + pendingItems.size + oldCount, added)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position 0 = account header card
|
// Position 0 = account header card
|
||||||
// Positions 1..displayItems.size = date headers + transactions
|
// Positions 1..pendingItems.size = pending header + pending transactions
|
||||||
|
// Positions 1+pendingItems.size..1+pendingItems.size+displayItems.size = date headers + transactions
|
||||||
// Last position = loading footer when showLoadingFooter = true
|
// Last position = loading footer when showLoadingFooter = true
|
||||||
override fun getItemCount() = 1 + displayItems.size + if (_showLoadingFooter) 1 else 0
|
override fun getItemCount() = 1 + pendingItems.size + displayItems.size + if (_showLoadingFooter) 1 else 0
|
||||||
|
|
||||||
|
private fun itemAt(position: Int): Item {
|
||||||
|
val idx = position - 1
|
||||||
|
return if (idx < pendingItems.size) pendingItems[idx] else displayItems[idx - pendingItems.size]
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when {
|
override fun getItemViewType(position: Int) = when {
|
||||||
position == 0 -> TYPE_HEADER
|
position == 0 -> TYPE_HEADER
|
||||||
_showLoadingFooter && position == itemCount - 1 -> TYPE_LOADING
|
_showLoadingFooter && position == itemCount - 1 -> TYPE_LOADING
|
||||||
else -> when (displayItems[position - 1]) {
|
else -> when (itemAt(position)) {
|
||||||
is Item.DateHeader -> TYPE_DATE_HEADER
|
is Item.DateHeader -> TYPE_DATE_HEADER
|
||||||
is Item.Trx -> TYPE_TRANSACTION
|
is Item.Trx -> TYPE_TRANSACTION
|
||||||
}
|
}
|
||||||
@@ -157,8 +175,11 @@ class AccountHistoryAdapter(
|
|||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is HeaderVH -> holder.bind(display)
|
is HeaderVH -> holder.bind(display)
|
||||||
is DateHeaderVH -> holder.bind((displayItems[position - 1] as Item.DateHeader).label)
|
is DateHeaderVH -> holder.bind((itemAt(position) as Item.DateHeader).label)
|
||||||
is TransactionVH -> holder.bind((displayItems[position - 1] as Item.Trx).transaction)
|
is TransactionVH -> {
|
||||||
|
val item = itemAt(position) as Item.Trx
|
||||||
|
holder.bind(item.transaction, item.showDate)
|
||||||
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +224,7 @@ class AccountHistoryAdapter(
|
|||||||
|
|
||||||
inner class TransactionVH(private val b: ItemTransactionBinding) :
|
inner class TransactionVH(private val b: ItemTransactionBinding) :
|
||||||
RecyclerView.ViewHolder(b.root) {
|
RecyclerView.ViewHolder(b.root) {
|
||||||
fun bind(trx: BankTransaction) {
|
fun bind(trx: BankTransaction, showDate: Boolean = false) {
|
||||||
val isCredit = trx.amount >= 0
|
val isCredit = trx.amount >= 0
|
||||||
val color = sourceColor(trx.source)
|
val color = sourceColor(trx.source)
|
||||||
val name = trx.counterpartyName ?: trx.description
|
val name = trx.counterpartyName ?: trx.description
|
||||||
@@ -239,7 +260,7 @@ class AccountHistoryAdapter(
|
|||||||
b.tvCounterparty.visibility = View.GONE
|
b.tvCounterparty.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
b.tvDate.text = formatTime(trx.date)
|
b.tvDate.text = if (showDate) formatDateOnly(trx.date) else formatTime(trx.date)
|
||||||
|
|
||||||
if (hideAmounts) {
|
if (hideAmounts) {
|
||||||
b.tvAmount.text = "${trx.currency} ••••••"
|
b.tvAmount.text = "${trx.currency} ••••••"
|
||||||
@@ -286,6 +307,7 @@ class AccountHistoryAdapter(
|
|||||||
private val MIB_FMT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
private val MIB_FMT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||||
private val BML_FMT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US)
|
private val BML_FMT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US)
|
||||||
private val DATE_HEADER_FMT = SimpleDateFormat("MMMM d, yyyy", Locale.getDefault())
|
private val DATE_HEADER_FMT = SimpleDateFormat("MMMM d, yyyy", Locale.getDefault())
|
||||||
|
private val DATE_ONLY_FMT = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
|
||||||
private val TIME_FMT = SimpleDateFormat("h:mm a", Locale.getDefault())
|
private val TIME_FMT = SimpleDateFormat("h:mm a", Locale.getDefault())
|
||||||
private val FULL_DATE_FMT = SimpleDateFormat("MMM d, yyyy · h:mm a", Locale.getDefault())
|
private val FULL_DATE_FMT = SimpleDateFormat("MMM d, yyyy · h:mm a", Locale.getDefault())
|
||||||
|
|
||||||
@@ -307,6 +329,11 @@ class AccountHistoryAdapter(
|
|||||||
return DATE_HEADER_FMT.format(date)
|
return DATE_HEADER_FMT.format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatDateOnly(raw: String): String {
|
||||||
|
val date = parseDate(raw) ?: return raw.take(10)
|
||||||
|
return DATE_ONLY_FMT.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
fun formatTime(raw: String): String {
|
fun formatTime(raw: String): String {
|
||||||
val date = parseDate(raw) ?: return ""
|
val date = parseDate(raw) ?: return ""
|
||||||
return TIME_FMT.format(date)
|
return TIME_FMT.format(date)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import sh.sar.basedbank.BasedBankApp
|
|||||||
import sh.sar.basedbank.R
|
import sh.sar.basedbank.R
|
||||||
import sh.sar.basedbank.api.models.BankAccount
|
import sh.sar.basedbank.api.models.BankAccount
|
||||||
import sh.sar.basedbank.api.models.BankServerException
|
import sh.sar.basedbank.api.models.BankServerException
|
||||||
|
import sh.sar.basedbank.api.bml.BmlHistoryClient
|
||||||
import sh.sar.basedbank.api.mib.MibContactsClient
|
import sh.sar.basedbank.api.mib.MibContactsClient
|
||||||
import sh.sar.basedbank.api.models.BankTransaction
|
import sh.sar.basedbank.api.models.BankTransaction
|
||||||
import sh.sar.basedbank.api.mib.TransactionCache
|
import sh.sar.basedbank.api.mib.TransactionCache
|
||||||
@@ -138,6 +139,7 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
(activity as? HomeActivity)?.setRefreshing(true)
|
(activity as? HomeActivity)?.setRefreshing(true)
|
||||||
loadNextPage()
|
loadNextPage()
|
||||||
|
loadPendingTransactions()
|
||||||
|
|
||||||
binding.swipeRefresh.setOnRefreshListener {
|
binding.swipeRefresh.setOnRefreshListener {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -184,6 +186,7 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
binding.emptyView.visibility = View.GONE
|
binding.emptyView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
loadNextPage()
|
loadNextPage()
|
||||||
|
loadPendingTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadNextPage() {
|
private fun loadNextPage() {
|
||||||
@@ -250,6 +253,26 @@ class AccountHistoryFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadPendingTransactions() {
|
||||||
|
if (account.bank != "BML" || account.profileType != "BML") return
|
||||||
|
val app = requireActivity().application as BasedBankApp
|
||||||
|
val session = app.bmlSessionFor(account) ?: return
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val pending = withContext(Dispatchers.IO) {
|
||||||
|
BmlHistoryClient().fetchPendingHistory(
|
||||||
|
session = session,
|
||||||
|
accountId = account.internalId,
|
||||||
|
accountDisplayName = account.accountBriefName,
|
||||||
|
accountNumber = account.accountNumber
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (_binding == null) return@launch
|
||||||
|
adapter.setPendingTransactions(pending)
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadContactImage(name: String) {
|
private fun loadContactImage(name: String) {
|
||||||
if (!pendingImageNames.add(name)) return
|
if (!pendingImageNames.add(name)) return
|
||||||
val contact = viewModel.contacts.value?.firstOrNull { it.benefNickName == name } ?: return
|
val contact = viewModel.contacts.value?.firstOrNull { it.benefNickName == name } ?: return
|
||||||
|
|||||||
Reference in New Issue
Block a user