added support for BML loans
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 3s
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 3s
This commit is contained in:
@@ -49,6 +49,30 @@ class BmlAccountClient {
|
||||
} catch (_: Exception) { null }
|
||||
}
|
||||
|
||||
fun fetchLoanDetail(session: BmlSession, internalId: String): BmlLoanDetail? {
|
||||
val resp = client.newCall(bmlApiRequest(session, "$BML_BASE_URL/api/mobile/account/$internalId")).execute()
|
||||
val code = resp.code
|
||||
val json = resp.body?.string() ?: return null
|
||||
resp.close()
|
||||
if (code == 401 || code == 419) throw AuthExpiredException()
|
||||
return try {
|
||||
val root = JSONObject(json)
|
||||
if (!root.optBoolean("success")) return null
|
||||
val p = root.optJSONObject("payload") ?: return null
|
||||
BmlLoanDetail(
|
||||
loanAmount = p.optDouble("loanAmount", 0.0),
|
||||
outstandingAmt = p.optDouble("outstandingAmt", 0.0),
|
||||
repayAmount = p.optDouble("repayAmount", 0.0),
|
||||
intRate = p.optDouble("intRate", 0.0),
|
||||
loanStatus = p.optString("loanStatus"),
|
||||
startDate = p.optString("startDate"),
|
||||
endDate = p.optString("endDate"),
|
||||
noOfRepayOverdue = p.optInt("noOfRepayOverdue", 0),
|
||||
overdueAmount = p.optDouble("overdueAmount", 0.0)
|
||||
)
|
||||
} catch (_: Exception) { null }
|
||||
}
|
||||
|
||||
private fun parseDashboard(
|
||||
json: String,
|
||||
loginTag: String,
|
||||
@@ -61,6 +85,7 @@ class BmlAccountClient {
|
||||
|
||||
val casaAccounts = mutableListOf<BankAccount>()
|
||||
val prepaidCards = mutableListOf<BankAccount>()
|
||||
val loanAccounts = mutableListOf<BankAccount>()
|
||||
|
||||
for (i in 0 until dashboard.length()) {
|
||||
val item = dashboard.getJSONObject(i)
|
||||
@@ -91,6 +116,26 @@ class BmlAccountClient {
|
||||
profileId = profileId,
|
||||
internalId = internalId
|
||||
))
|
||||
} else if (accountType == "Loan") {
|
||||
val outstanding = Math.abs(item.optDouble("availableBalance", 0.0))
|
||||
loanAccounts.add(BankAccount(
|
||||
bank = "BML",
|
||||
profileName = profileName,
|
||||
profileType = "BML_LOAN",
|
||||
accountNumber = accountNumber,
|
||||
accountBriefName = item.optString("alias"),
|
||||
currencyName = currency,
|
||||
accountTypeName = product,
|
||||
availableBalance = "%.2f".format(outstanding),
|
||||
currentBalance = "%.2f".format(outstanding),
|
||||
blockedAmount = "0.00",
|
||||
mvrBalance = "0.00",
|
||||
statusDesc = status,
|
||||
profileImageHash = null,
|
||||
loginTag = loginTag,
|
||||
profileId = profileId,
|
||||
internalId = internalId
|
||||
))
|
||||
} else if (accountType == "Card") {
|
||||
val isVisible = item.optBoolean("account_visible", false)
|
||||
if (!isVisible) continue
|
||||
@@ -119,6 +164,6 @@ class BmlAccountClient {
|
||||
}
|
||||
}
|
||||
|
||||
return casaAccounts + prepaidCards
|
||||
return casaAccounts + prepaidCards + loanAccounts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,18 @@ data class BmlTransferResult(
|
||||
val errorMessage: String = ""
|
||||
)
|
||||
|
||||
data class BmlLoanDetail(
|
||||
val loanAmount: Double,
|
||||
val outstandingAmt: Double, // negative as returned by API
|
||||
val repayAmount: Double,
|
||||
val intRate: Double,
|
||||
val loanStatus: String,
|
||||
val startDate: String, // ISO8601 e.g. "2023-10-26T00:00:00+05:00"
|
||||
val endDate: String,
|
||||
val noOfRepayOverdue: Int,
|
||||
val overdueAmount: Double
|
||||
)
|
||||
|
||||
data class BmlForeignLimit(
|
||||
val type: String,
|
||||
val used: Double,
|
||||
|
||||
@@ -14,6 +14,7 @@ import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.BmlForeignLimit
|
||||
import sh.sar.basedbank.api.models.BankAccount
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
import kotlin.math.abs
|
||||
import sh.sar.basedbank.databinding.FragmentDashboardBinding
|
||||
import sh.sar.basedbank.databinding.ItemForeignLimitBinding
|
||||
|
||||
@@ -30,11 +31,12 @@ class DashboardFragment : Fragment() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
viewModel.accounts.observe(viewLifecycleOwner) { updateBalances(it) }
|
||||
viewModel.financing.observe(viewLifecycleOwner) { updatePendingFinances(it) }
|
||||
viewModel.financing.observe(viewLifecycleOwner) { updatePendingFinances() }
|
||||
viewModel.bmlLoanDetails.observe(viewLifecycleOwner) { updatePendingFinances() }
|
||||
viewModel.bmlLimits.observe(viewLifecycleOwner) { updateForeignLimits(it) }
|
||||
viewModel.hideAmounts.observe(viewLifecycleOwner) {
|
||||
updateBalances(viewModel.accounts.value ?: emptyList())
|
||||
updatePendingFinances(viewModel.financing.value ?: emptyList())
|
||||
updatePendingFinances()
|
||||
updateForeignLimits(viewModel.bmlLimits.value ?: emptyList())
|
||||
}
|
||||
|
||||
@@ -123,9 +125,13 @@ class DashboardFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePendingFinances(deals: List<MibFinanceDeal>) {
|
||||
private fun updatePendingFinances() {
|
||||
val hide = viewModel.hideAmounts.value ?: false
|
||||
binding.tvPendingFinances.text = if (hide) "MVR ••••••" else "MVR %,.2f".format(deals.sumOf { it.outstandingAmount })
|
||||
val mibTotal = (viewModel.financing.value ?: emptyList()).sumOf { it.outstandingAmount }
|
||||
val bmlLoanDetails = viewModel.bmlLoanDetails.value ?: emptyMap()
|
||||
val bmlTotal = bmlLoanDetails.values.sumOf { abs(it.outstandingAmt) }
|
||||
val total = mibTotal + bmlTotal
|
||||
binding.tvPendingFinances.text = if (hide) "MVR ••••••" else "MVR %,.2f".format(total)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -5,56 +5,97 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.BmlLoanDetail
|
||||
import sh.sar.basedbank.api.models.BankAccount
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
import sh.sar.basedbank.api.mib.MibFinancingClient
|
||||
import sh.sar.basedbank.databinding.ItemBmlLoanBinding
|
||||
import sh.sar.basedbank.databinding.ItemFinanceDealBinding
|
||||
import java.text.NumberFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
|
||||
class FinancingAdapter(private var deals: List<MibFinanceDeal>) :
|
||||
RecyclerView.Adapter<FinancingAdapter.ViewHolder>() {
|
||||
class FinancingAdapter(mibDeals: List<MibFinanceDeal>) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private sealed class Item {
|
||||
data class Mib(val deal: MibFinanceDeal) : Item()
|
||||
data class Bml(val account: BankAccount, val detail: BmlLoanDetail?) : Item()
|
||||
}
|
||||
|
||||
private var items: List<Item> = mibDeals.map { Item.Mib(it) }
|
||||
private var hideAmounts: Boolean = false
|
||||
|
||||
private val expandedPositions = mutableSetOf<Int>()
|
||||
private val amountFmt = NumberFormat.getNumberInstance(Locale.US).apply {
|
||||
minimumFractionDigits = 2
|
||||
maximumFractionDigits = 2
|
||||
}
|
||||
private val mibDateFmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
private val isoDateFmt = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US)
|
||||
private val outputDateFmt = SimpleDateFormat("d MMM yyyy", Locale.US)
|
||||
|
||||
fun setHideAmounts(hide: Boolean) {
|
||||
if (hideAmounts == hide) return
|
||||
hideAmounts = hide
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private val expandedPositions = mutableSetOf<Int>()
|
||||
private val amountFmt = NumberFormat.getNumberInstance(Locale.US).apply {
|
||||
minimumFractionDigits = 2
|
||||
maximumFractionDigits = 2
|
||||
}
|
||||
private val inputDateFmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
private val outputDateFmt = SimpleDateFormat("d MMM yyyy", Locale.US)
|
||||
|
||||
fun updateDeals(newDeals: List<MibFinanceDeal>) {
|
||||
deals = newDeals
|
||||
fun update(mibDeals: List<MibFinanceDeal>, bmlLoans: List<Pair<BankAccount, BmlLoanDetail?>>) {
|
||||
expandedPositions.clear()
|
||||
items = mibDeals.map { Item.Mib(it) } + bmlLoans.map { (acc, detail) -> Item.Bml(acc, detail) }
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = ItemFinanceDealBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding)
|
||||
// Legacy compatibility — used on initial empty construction
|
||||
fun updateDeals(newDeals: List<MibFinanceDeal>) {
|
||||
expandedPositions.clear()
|
||||
val bmlItems = items.filterIsInstance<Item.Bml>()
|
||||
items = newDeals.map { Item.Mib(it) } + bmlItems
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(deals[position], position in expandedPositions)
|
||||
holder.binding.root.setOnClickListener {
|
||||
fun updateBmlLoans(loans: List<Pair<BankAccount, BmlLoanDetail?>>) {
|
||||
expandedPositions.clear()
|
||||
val mibItems = items.filterIsInstance<Item.Mib>()
|
||||
items = mibItems + loans.map { (acc, detail) -> Item.Bml(acc, detail) }
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int) = when (items[position]) {
|
||||
is Item.Mib -> TYPE_MIB
|
||||
is Item.Bml -> TYPE_BML
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
TYPE_BML -> BmlViewHolder(ItemBmlLoanBinding.inflate(inflater, parent, false))
|
||||
else -> MibViewHolder(ItemFinanceDealBinding.inflate(inflater, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val expanded = position in expandedPositions
|
||||
when (val item = items[position]) {
|
||||
is Item.Mib -> (holder as MibViewHolder).bind(item.deal, expanded)
|
||||
is Item.Bml -> (holder as BmlViewHolder).bind(item.account, item.detail, expanded)
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
val pos = holder.bindingAdapterPosition
|
||||
if (pos in expandedPositions) expandedPositions.remove(pos) else expandedPositions.add(pos)
|
||||
notifyItemChanged(pos)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = deals.size
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
inner class ViewHolder(val binding: ItemFinanceDealBinding) :
|
||||
// ── MIB ViewHolder ────────────────────────────────────────────────────────
|
||||
|
||||
inner class MibViewHolder(val binding: ItemFinanceDealBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(deal: MibFinanceDeal, expanded: Boolean) {
|
||||
@@ -69,25 +110,22 @@ class FinancingAdapter(private var deals: List<MibFinanceDeal>) :
|
||||
binding.tvPaid.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(deal.paidAmount)}"
|
||||
binding.tvUnpaid.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(deal.outstandingAmount)}"
|
||||
|
||||
// Progress bar
|
||||
val progress = if (deal.dealAmount > 0)
|
||||
((deal.paidAmount / deal.dealAmount) * 100).toInt().coerceIn(0, 100)
|
||||
else 0
|
||||
binding.progressBar.progress = if (hide) 0 else progress
|
||||
|
||||
// Completion estimate
|
||||
binding.tvCompletion.text = completionText(deal, ctx)
|
||||
binding.tvCompletion.text = mibCompletionText(deal, ctx)
|
||||
|
||||
// Expanded details
|
||||
val detailsVisible = if (expanded) View.VISIBLE else View.GONE
|
||||
binding.dividerDetails.visibility = detailsVisible
|
||||
binding.detailsGroup.visibility = detailsVisible
|
||||
|
||||
if (expanded) {
|
||||
binding.tvDealDate.text = formatDate(deal.dealDate)
|
||||
binding.tvDealDate.text = formatMibDate(deal.dealDate)
|
||||
binding.tvInstallment.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(deal.installmentAmount)}"
|
||||
binding.tvNumInstallments.text = deal.noOfInstallments.toString()
|
||||
binding.tvLastPaidDate.text = formatDate(deal.lastPaidDate)
|
||||
binding.tvLastPaidDate.text = formatMibDate(deal.lastPaidDate)
|
||||
binding.tvLastPayAmount.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(deal.lastPayAmount)}"
|
||||
|
||||
if (deal.overdueAmount > 0) {
|
||||
@@ -99,7 +137,7 @@ class FinancingAdapter(private var deals: List<MibFinanceDeal>) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun completionText(deal: MibFinanceDeal, ctx: android.content.Context): String {
|
||||
private fun mibCompletionText(deal: MibFinanceDeal, ctx: android.content.Context): String {
|
||||
if (deal.outstandingAmount <= 0.0) return ctx.getString(R.string.financing_completion_done)
|
||||
val remaining = MibFinancingClient.remainingMonths(deal)
|
||||
if (remaining <= 0) return ctx.getString(R.string.financing_completion_done)
|
||||
@@ -109,12 +147,84 @@ class FinancingAdapter(private var deals: List<MibFinanceDeal>) :
|
||||
return ctx.getString(R.string.financing_completion_fmt, month)
|
||||
}
|
||||
|
||||
private fun formatDate(raw: String): String {
|
||||
private fun formatMibDate(raw: String): String {
|
||||
return try {
|
||||
outputDateFmt.format(inputDateFmt.parse(raw)!!)
|
||||
} catch (_: Exception) {
|
||||
raw.take(10)
|
||||
}
|
||||
outputDateFmt.format(mibDateFmt.parse(raw)!!)
|
||||
} catch (_: Exception) { raw.take(10) }
|
||||
}
|
||||
}
|
||||
|
||||
// ── BML ViewHolder ────────────────────────────────────────────────────────
|
||||
|
||||
inner class BmlViewHolder(val binding: ItemBmlLoanBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(account: BankAccount, detail: BmlLoanDetail?, expanded: Boolean) {
|
||||
val ctx = binding.root.context
|
||||
val currency = account.currencyName
|
||||
val hide = hideAmounts
|
||||
|
||||
binding.tvLoanProduct.text = account.accountTypeName
|
||||
.trim().lowercase().split(" ")
|
||||
.joinToString(" ") { it.replaceFirstChar { c -> c.uppercaseChar() } }
|
||||
binding.tvLoanAccount.text = account.accountNumber
|
||||
binding.tvLoanStatus.text = detail?.loanStatus?.ifBlank { account.statusDesc } ?: account.statusDesc
|
||||
|
||||
val loanAmt = detail?.loanAmount ?: 0.0
|
||||
val outstanding = if (detail != null) abs(detail.outstandingAmt) else account.availableBalance.toDoubleOrNull() ?: 0.0
|
||||
val paid = (loanAmt - outstanding).coerceAtLeast(0.0)
|
||||
|
||||
binding.tvLoanTotal.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(loanAmt)}"
|
||||
binding.tvLoanPaid.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(paid)}"
|
||||
binding.tvLoanOutstanding.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(outstanding)}"
|
||||
|
||||
val progress = if (loanAmt > 0) ((paid / loanAmt) * 100).toInt().coerceIn(0, 100) else 0
|
||||
binding.loanProgressBar.progress = if (hide) 0 else progress
|
||||
|
||||
binding.tvLoanCompletion.text = bmlCompletionText(detail, ctx)
|
||||
|
||||
val detailsVisible = if (expanded) View.VISIBLE else View.GONE
|
||||
binding.loanDividerDetails.visibility = detailsVisible
|
||||
binding.loanDetailsGroup.visibility = detailsVisible
|
||||
|
||||
if (expanded && detail != null) {
|
||||
binding.tvLoanRepayment.text = if (hide) "$currency ••••••" else "$currency ${amountFmt.format(detail.repayAmount)}"
|
||||
binding.tvLoanIntRate.text = ctx.getString(R.string.loan_rate_fmt, detail.intRate)
|
||||
binding.tvLoanStartDate.text = formatIsoDate(detail.startDate)
|
||||
binding.tvLoanEndDate.text = formatIsoDate(detail.endDate)
|
||||
|
||||
if (detail.overdueAmount > 0) {
|
||||
binding.loanRowOverdue.visibility = View.VISIBLE
|
||||
binding.tvLoanOverdue.text = if (hide) "$currency ••••••"
|
||||
else "$currency ${amountFmt.format(detail.overdueAmount)} (${detail.noOfRepayOverdue})"
|
||||
} else {
|
||||
binding.loanRowOverdue.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bmlCompletionText(detail: BmlLoanDetail?, ctx: android.content.Context): String {
|
||||
if (detail == null) return ""
|
||||
val outstanding = abs(detail.outstandingAmt)
|
||||
if (outstanding <= 0.0 || detail.repayAmount <= 0.0)
|
||||
return ctx.getString(R.string.financing_completion_done)
|
||||
val remaining = ceil(outstanding / detail.repayAmount).toInt()
|
||||
if (remaining <= 0) return ctx.getString(R.string.financing_completion_done)
|
||||
val cal = Calendar.getInstance()
|
||||
cal.add(Calendar.MONTH, remaining)
|
||||
val month = SimpleDateFormat("MMMM yyyy", Locale.getDefault()).format(cal.time)
|
||||
return ctx.getString(R.string.financing_completion_fmt, month)
|
||||
}
|
||||
|
||||
private fun formatIsoDate(raw: String): String {
|
||||
return try {
|
||||
outputDateFmt.format(isoDateFmt.parse(raw)!!)
|
||||
} catch (_: Exception) { raw.take(10) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_MIB = 0
|
||||
private const val TYPE_BML = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.BmlLoanDetail
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
import sh.sar.basedbank.api.models.BankAccount
|
||||
import sh.sar.basedbank.databinding.FragmentFinancingBinding
|
||||
|
||||
class FinancingFragment : Fragment() {
|
||||
@@ -20,6 +23,9 @@ class FinancingFragment : Fragment() {
|
||||
private lateinit var adapter: FinancingAdapter
|
||||
private var financingRefreshing = false
|
||||
|
||||
private var latestMibDeals: List<MibFinanceDeal> = emptyList()
|
||||
private var latestBmlLoanDetails: Map<String, BmlLoanDetail> = emptyMap()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentFinancingBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
@@ -44,19 +50,36 @@ class FinancingFragment : Fragment() {
|
||||
(activity as? HomeActivity)?.triggerRefreshFinancing()
|
||||
}
|
||||
|
||||
viewModel.accounts.observe(viewLifecycleOwner) { rebuildAdapter() }
|
||||
viewModel.financing.observe(viewLifecycleOwner) { deals ->
|
||||
adapter.updateDeals(deals)
|
||||
binding.recyclerView.visibility = if (deals.isEmpty()) View.GONE else View.VISIBLE
|
||||
binding.emptyView.visibility = if (deals.isEmpty()) View.VISIBLE else View.GONE
|
||||
binding.loadingView.visibility = View.GONE
|
||||
if (financingRefreshing) {
|
||||
financingRefreshing = false
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
}
|
||||
latestMibDeals = deals
|
||||
rebuildAdapter()
|
||||
}
|
||||
viewModel.bmlLoanDetails.observe(viewLifecycleOwner) { details ->
|
||||
latestBmlLoanDetails = details
|
||||
rebuildAdapter()
|
||||
}
|
||||
viewModel.hideAmounts.observe(viewLifecycleOwner) { adapter.setHideAmounts(it) }
|
||||
}
|
||||
|
||||
private fun rebuildAdapter() {
|
||||
val accounts = viewModel.accounts.value ?: emptyList()
|
||||
val loanAccounts = accounts.filter { it.profileType == "BML_LOAN" }
|
||||
val bmlLoans: List<Pair<BankAccount, BmlLoanDetail?>> =
|
||||
loanAccounts.map { acc -> acc to latestBmlLoanDetails[acc.internalId] }
|
||||
|
||||
adapter.update(latestMibDeals, bmlLoans)
|
||||
|
||||
val isEmpty = latestMibDeals.isEmpty() && bmlLoans.isEmpty()
|
||||
binding.recyclerView.visibility = if (isEmpty) View.GONE else View.VISIBLE
|
||||
binding.emptyView.visibility = if (isEmpty) View.VISIBLE else View.GONE
|
||||
binding.loadingView.visibility = View.GONE
|
||||
if (financingRefreshing) {
|
||||
financingRefreshing = false
|
||||
binding.swipeRefresh.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
requireActivity().title = getString(R.string.nav_finances)
|
||||
|
||||
@@ -40,6 +40,7 @@ import sh.sar.basedbank.api.bml.BmlAccountClient
|
||||
import sh.sar.basedbank.api.bml.BmlActivationResult
|
||||
import sh.sar.basedbank.api.bml.BmlContactsClient
|
||||
import sh.sar.basedbank.api.bml.BmlForeignLimitsClient
|
||||
import sh.sar.basedbank.api.bml.BmlLoanDetail
|
||||
import sh.sar.basedbank.api.bml.BmlProfile
|
||||
import sh.sar.basedbank.api.bml.BmlSession
|
||||
import sh.sar.basedbank.api.fahipay.FahipayAccountClient
|
||||
@@ -170,6 +171,8 @@ class HomeActivity : AppCompatActivity() {
|
||||
|
||||
val cachedFinancing = FinancingCache.load(this)
|
||||
if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing
|
||||
val cachedBmlLoans = FinancingCache.loadBmlLoans(this)
|
||||
if (cachedBmlLoans.isNotEmpty()) viewModel.bmlLoanDetails.value = cachedBmlLoans
|
||||
val cachedLimits = ForeignLimitsCache.load(this)
|
||||
if (cachedLimits.isNotEmpty()) viewModel.bmlLimits.value = cachedLimits
|
||||
|
||||
@@ -178,6 +181,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
refreshFinancing(loginId, session, profiles.filterVisibleProfiles(loginId))
|
||||
}
|
||||
for ((_, session) in app.bmlSessions) refreshBmlLimits(session)
|
||||
refreshBmlLoanDetails()
|
||||
} else {
|
||||
// Came from lock screen — show caches immediately, refresh everything in background
|
||||
val store = CredentialStore(this)
|
||||
@@ -188,6 +192,8 @@ class HomeActivity : AppCompatActivity() {
|
||||
if (merged.isNotEmpty()) viewModel.accounts.value = merged
|
||||
val cachedFinancing = FinancingCache.load(this)
|
||||
if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing
|
||||
val cachedBmlLoans = FinancingCache.loadBmlLoans(this)
|
||||
if (cachedBmlLoans.isNotEmpty()) viewModel.bmlLoanDetails.value = cachedBmlLoans
|
||||
val cachedLimits = ForeignLimitsCache.load(this)
|
||||
if (cachedLimits.isNotEmpty()) viewModel.bmlLimits.value = cachedLimits
|
||||
|
||||
@@ -334,6 +340,7 @@ fun applyNavLabelVisibility() {
|
||||
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
|
||||
refreshFinancing(loginId, session, profiles.filterVisibleProfiles(loginId))
|
||||
}
|
||||
refreshBmlLoanDetails()
|
||||
}
|
||||
|
||||
fun setRefreshing(visible: Boolean) {
|
||||
@@ -661,6 +668,7 @@ fun applyNavLabelVisibility() {
|
||||
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
|
||||
refreshFinancing(loginId, session, profiles.filterVisibleProfiles(loginId))
|
||||
}
|
||||
refreshBmlLoanDetails()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,6 +906,32 @@ fun applyNavLabelVisibility() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshBmlLoanDetails() {
|
||||
val app = application as BasedBankApp
|
||||
val loanAccounts = app.bmlAccounts.filter { it.profileType == "BML_LOAN" }
|
||||
if (loanAccounts.isEmpty()) return
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val details = withContext(Dispatchers.IO) {
|
||||
val map = mutableMapOf<String, BmlLoanDetail>()
|
||||
for (acc in loanAccounts) {
|
||||
val session = app.bmlSessionFor(acc) ?: continue
|
||||
try {
|
||||
val detail = BmlAccountClient().fetchLoanDetail(session, acc.internalId)
|
||||
if (detail != null) map[acc.internalId] = detail
|
||||
} catch (_: Exception) { /* keep existing */ }
|
||||
}
|
||||
map
|
||||
}
|
||||
if (details.isNotEmpty()) {
|
||||
val merged = (viewModel.bmlLoanDetails.value ?: emptyMap()) + details
|
||||
FinancingCache.saveBmlLoans(this@HomeActivity, merged)
|
||||
viewModel.bmlLoanDetails.postValue(merged)
|
||||
}
|
||||
} catch (_: Exception) { /* keep cached data */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshFinancing(loginId: String, session: MibSession, profiles: List<MibProfile>) {
|
||||
if (profiles.isEmpty()) return
|
||||
val flow = (application as BasedBankApp).mibFlowFor(loginId)
|
||||
|
||||
@@ -3,6 +3,7 @@ package sh.sar.basedbank.ui.home
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import sh.sar.basedbank.api.bml.BmlForeignLimit
|
||||
import sh.sar.basedbank.api.bml.BmlLoanDetail
|
||||
import sh.sar.basedbank.api.models.BankAccount
|
||||
import sh.sar.basedbank.api.models.BankContact
|
||||
import sh.sar.basedbank.api.models.BankContactCategory
|
||||
@@ -11,6 +12,8 @@ import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
class HomeViewModel : ViewModel() {
|
||||
val accounts = MutableLiveData<List<BankAccount>>(emptyList())
|
||||
val financing = MutableLiveData<List<MibFinanceDeal>>(emptyList())
|
||||
/** BML loan details keyed by account internalId. */
|
||||
val bmlLoanDetails = MutableLiveData<Map<String, BmlLoanDetail>>(emptyMap())
|
||||
val contacts = MutableLiveData<List<BankContact>>(emptyList())
|
||||
val contactCategories = MutableLiveData<List<BankContactCategory>>(emptyList())
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ package sh.sar.basedbank.util
|
||||
import android.content.Context
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import sh.sar.basedbank.api.bml.BmlLoanDetail
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
|
||||
object FinancingCache {
|
||||
|
||||
private const val PREFS = "financing_cache"
|
||||
private const val KEY_MIB = "mib_financing"
|
||||
private const val KEY_BML_LOANS = "bml_loans"
|
||||
|
||||
fun save(context: Context, deals: List<MibFinanceDeal>) {
|
||||
val arr = JSONArray()
|
||||
@@ -34,6 +36,52 @@ object FinancingCache {
|
||||
.edit().putString(KEY_MIB, CacheEncryption.encrypt(arr.toString())).apply()
|
||||
}
|
||||
|
||||
fun saveBmlLoans(context: Context, loans: Map<String, BmlLoanDetail>) {
|
||||
val arr = JSONArray()
|
||||
for ((internalId, d) in loans) {
|
||||
arr.put(JSONObject().apply {
|
||||
put("internalId", internalId)
|
||||
put("loanAmount", d.loanAmount)
|
||||
put("outstandingAmt", d.outstandingAmt)
|
||||
put("repayAmount", d.repayAmount)
|
||||
put("intRate", d.intRate)
|
||||
put("loanStatus", d.loanStatus)
|
||||
put("startDate", d.startDate)
|
||||
put("endDate", d.endDate)
|
||||
put("noOfRepayOverdue", d.noOfRepayOverdue)
|
||||
put("overdueAmount", d.overdueAmount)
|
||||
})
|
||||
}
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.edit().putString(KEY_BML_LOANS, CacheEncryption.encrypt(arr.toString())).apply()
|
||||
}
|
||||
|
||||
fun loadBmlLoans(context: Context): Map<String, BmlLoanDetail> {
|
||||
val raw = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.getString(KEY_BML_LOANS, null) ?: return emptyMap()
|
||||
return try {
|
||||
val json = CacheEncryption.decrypt(raw)
|
||||
val arr = JSONArray(json)
|
||||
buildMap {
|
||||
for (i in 0 until arr.length()) {
|
||||
val o = arr.getJSONObject(i)
|
||||
val id = o.optString("internalId")
|
||||
if (id.isNotBlank()) put(id, BmlLoanDetail(
|
||||
loanAmount = o.optDouble("loanAmount", 0.0),
|
||||
outstandingAmt = o.optDouble("outstandingAmt", 0.0),
|
||||
repayAmount = o.optDouble("repayAmount", 0.0),
|
||||
intRate = o.optDouble("intRate", 0.0),
|
||||
loanStatus = o.optString("loanStatus"),
|
||||
startDate = o.optString("startDate"),
|
||||
endDate = o.optString("endDate"),
|
||||
noOfRepayOverdue = o.optInt("noOfRepayOverdue", 0),
|
||||
overdueAmount = o.optDouble("overdueAmount", 0.0)
|
||||
))
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) { emptyMap() }
|
||||
}
|
||||
|
||||
fun clear(context: Context) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().clear().apply()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class HistoryFetcher(private val account: BankAccount) {
|
||||
|
||||
private val isMib get() = account.bank == "MIB"
|
||||
private val isBmlCard get() = account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT"
|
||||
private val isBmlLoan get() = account.profileType == "BML_LOAN"
|
||||
private val isFahipay get() = account.bank == "FAHIPAY"
|
||||
|
||||
// MIB pagination
|
||||
@@ -40,6 +41,7 @@ class HistoryFetcher(private val account: BankAccount) {
|
||||
private var fahipayTotal = -1
|
||||
|
||||
fun hasMore(): Boolean = when {
|
||||
isBmlLoan -> false
|
||||
isFahipay -> fahipayTotal < 0 || fahipayNextStart < fahipayTotal
|
||||
isMib -> mibTotalCount < 0 || mibNextStart <= mibTotalCount
|
||||
isBmlCard -> cardMonthOffset < 3
|
||||
@@ -47,6 +49,7 @@ class HistoryFetcher(private val account: BankAccount) {
|
||||
}
|
||||
|
||||
suspend fun fetchNextPage(app: BasedBankApp, pageSize: Int = 10): List<BankTransaction> = when {
|
||||
isBmlLoan -> emptyList()
|
||||
isFahipay -> withContext(Dispatchers.IO) { fetchFahipay(app) }
|
||||
isMib -> app.mibMutex.withLock { withContext(Dispatchers.IO) { fetchMib(app, pageSize) } }
|
||||
isBmlCard -> withContext(Dispatchers.IO) { fetchBmlCard(app) }
|
||||
|
||||
@@ -10,7 +10,8 @@ object BmlDashboardParser {
|
||||
* Returns all display fields for an account/card row in the accounts list.
|
||||
* Handles both BML CASA accounts and BML prepaid/credit cards.
|
||||
*/
|
||||
fun displayData(account: BankAccount): AccountListDisplay {
|
||||
fun displayData(account: BankAccount): AccountListDisplay? {
|
||||
if (account.profileType == "BML_LOAN") return null // Loans shown on financing page only
|
||||
val isCard = account.profileType == "BML_PREPAID" || account.profileType == "BML_CREDIT"
|
||||
return if (isCard) {
|
||||
val isActive = account.statusDesc.equals("Active", ignoreCase = true)
|
||||
|
||||
304
app/src/main/res/layout/item_bml_loan.xml
Normal file
304
app/src/main/res/layout/item_bml_loan.xml
Normal file
@@ -0,0 +1,304 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="?attr/colorOutlineVariant">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- Header row: product name + status chip -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanProduct"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanAccount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant"
|
||||
android:layout_marginTop="2dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:background="@drawable/chip_background"
|
||||
android:textAppearance="?attr/textAppearanceLabelSmall"
|
||||
android:textColor="?attr/colorOnSecondaryContainer" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Loan amount -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/financing_total"
|
||||
android:textAppearance="?attr/textAppearanceLabelSmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanTotal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:layout_marginStart="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Progress bar -->
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/loanProgressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:trackCornerRadius="4dp"
|
||||
app:trackThickness="8dp" />
|
||||
|
||||
<!-- Paid / Outstanding row -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/financing_paid"
|
||||
android:textAppearance="?attr/textAppearanceLabelSmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanPaid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:layout_marginTop="2dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/loan_outstanding"
|
||||
android:textAppearance="?attr/textAppearanceLabelSmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanOutstanding"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:textColor="@color/color_unpaid"
|
||||
android:layout_marginTop="2dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Completion estimate -->
|
||||
<TextView
|
||||
android:id="@+id/tvLoanCompletion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<!-- Divider -->
|
||||
<View
|
||||
android:id="@+id/loanDividerDetails"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/colorOutlineVariant"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Expanded details -->
|
||||
<LinearLayout
|
||||
android:id="@+id/loanDetailsGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<!-- Monthly repayment -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/loan_monthly_repayment"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanRepayment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Interest rate -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/loan_interest_rate"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanIntRate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Start date -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/loan_start_date"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanStartDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- End date -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/loan_end_date"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanEndDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Overdue (only shown when > 0) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/loanRowOverdue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/loan_overdue_payments"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="@color/color_unpaid" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoanOverdue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="@color/color_unpaid" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -261,4 +261,13 @@
|
||||
<string name="financing_completion_done">Fully paid</string>
|
||||
<string name="financing_deal_no_fmt">Deal #%s</string>
|
||||
<string name="financing_completion_fmt">Completes %s</string>
|
||||
|
||||
<!-- BML Loans -->
|
||||
<string name="loan_outstanding">Outstanding</string>
|
||||
<string name="loan_monthly_repayment">Monthly Repayment</string>
|
||||
<string name="loan_interest_rate">Interest Rate</string>
|
||||
<string name="loan_start_date">Start Date</string>
|
||||
<string name="loan_end_date">End Date</string>
|
||||
<string name="loan_overdue_payments">Overdue Payments</string>
|
||||
<string name="loan_rate_fmt">%.2f%%</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user