add BML usd limits to dashboard and optmize login flow/session expirey
This commit is contained in:
@@ -8,9 +8,11 @@ import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.BmlForeignLimit
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
import sh.sar.basedbank.api.mib.MibFinanceDeal
|
||||
import sh.sar.basedbank.databinding.FragmentDashboardBinding
|
||||
import sh.sar.basedbank.databinding.ItemForeignLimitBinding
|
||||
|
||||
class DashboardFragment : Fragment() {
|
||||
|
||||
@@ -26,6 +28,7 @@ class DashboardFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
viewModel.accounts.observe(viewLifecycleOwner) { updateBalances(it) }
|
||||
viewModel.financing.observe(viewLifecycleOwner) { updatePendingFinances(it) }
|
||||
viewModel.bmlLimits.observe(viewLifecycleOwner) { updateForeignLimits(it) }
|
||||
|
||||
binding.btnTransfer.setOnClickListener {
|
||||
(requireActivity() as HomeActivity).showWithBackStack(TransferFragment())
|
||||
@@ -52,6 +55,29 @@ class DashboardFragment : Fragment() {
|
||||
binding.tvUsdBalance.text = "USD %,.2f".format(usdTotal)
|
||||
}
|
||||
|
||||
private fun updateForeignLimits(entries: List<HomeViewModel.BmlLimitsData>) {
|
||||
binding.containerForeignLimits.removeAllViews()
|
||||
for (entry in entries) {
|
||||
for (limit in entry.limits) {
|
||||
val card = ItemForeignLimitBinding.inflate(layoutInflater, binding.containerForeignLimits, false)
|
||||
card.tvLimitUserName.text = entry.userName.ifBlank { "BML" }
|
||||
card.tvLimitType.text = limit.type
|
||||
card.tvLimitGeneral.text = "USD %,.0f / %,.0f".format(limit.generalRemaining, limit.generalCap)
|
||||
card.tvLimitMedical.text = "USD %,.0f".format(limit.medicalRemaining)
|
||||
card.tvLimitAtm.text = if (!limit.isAtmEnabled)
|
||||
"USD %,.0f / %,.0f · Disabled".format(limit.atmRemaining, limit.atmLimit)
|
||||
else
|
||||
"USD %,.0f / %,.0f".format(limit.atmRemaining, limit.atmLimit)
|
||||
card.tvLimitEcom.text = "USD %,.0f / %,.0f".format(limit.ecomRemaining, limit.ecomLimit)
|
||||
card.tvLimitPos.text = if (!limit.isPosEnabled)
|
||||
"USD %,.0f / %,.0f · Disabled".format(limit.posRemaining, limit.posLimit)
|
||||
else
|
||||
"USD %,.0f / %,.0f".format(limit.posRemaining, limit.posLimit)
|
||||
binding.containerForeignLimits.addView(card.root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePendingFinances(deals: List<MibFinanceDeal>) {
|
||||
val total = deals.sumOf { it.outstandingAmount }
|
||||
binding.tvPendingFinances.text = "MVR %,.2f".format(total)
|
||||
|
||||
@@ -10,11 +10,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import sh.sar.basedbank.BasedBankApp
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.AuthExpiredException
|
||||
import sh.sar.basedbank.api.bml.BmlLoginFlow
|
||||
import sh.sar.basedbank.api.bml.BmlSession
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||
import sh.sar.basedbank.databinding.ActivityHomeBinding
|
||||
@@ -30,6 +33,7 @@ import sh.sar.basedbank.util.AccountCache
|
||||
import sh.sar.basedbank.util.ContactsCache
|
||||
import sh.sar.basedbank.util.CredentialStore
|
||||
import sh.sar.basedbank.util.FinancingCache
|
||||
import sh.sar.basedbank.util.ForeignLimitsCache
|
||||
|
||||
class HomeActivity : AppCompatActivity() {
|
||||
|
||||
@@ -82,7 +86,10 @@ class HomeActivity : AppCompatActivity() {
|
||||
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
if (app.bmlSession != null) refreshBmlContacts(app)
|
||||
if (app.bmlSession != null) {
|
||||
refreshBmlContacts(app)
|
||||
refreshBmlLimits(app.bmlSession!!)
|
||||
}
|
||||
} else {
|
||||
// Came from lock screen — show caches immediately, refresh everything in background
|
||||
val cachedMib = AccountCache.load(this)
|
||||
@@ -97,10 +104,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
if (cachedCats.isNotEmpty()) viewModel.contactCategories.value = cachedCats
|
||||
|
||||
val store = CredentialStore(this)
|
||||
val mibCreds = store.loadMibCredentials()
|
||||
val bmlCreds = store.loadBmlCredentials()
|
||||
if (mibCreds != null) autoRefreshMib(mibCreds, bmlCreds)
|
||||
else if (bmlCreds != null) autoRefreshBml(bmlCreds)
|
||||
autoRefresh(store.loadMibCredentials(), store.loadBmlCredentials(), store)
|
||||
}
|
||||
|
||||
// Show dashboard on first create
|
||||
@@ -123,67 +127,99 @@ class HomeActivity : AppCompatActivity() {
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun autoRefreshMib(
|
||||
mibCreds: CredentialStore.MibCredentials,
|
||||
bmlCreds: CredentialStore.BmlCredentials?
|
||||
private fun autoRefresh(
|
||||
mibCreds: CredentialStore.MibCredentials?,
|
||||
bmlCreds: CredentialStore.BmlCredentials?,
|
||||
store: CredentialStore
|
||||
) {
|
||||
if (mibCreds == null && bmlCreds == null) return
|
||||
binding.refreshIndicator.visibility = View.VISIBLE
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
lifecycleScope.launch {
|
||||
var mibAccounts: List<MibAccount> = AccountCache.load(this@HomeActivity)
|
||||
try {
|
||||
mibAccounts = withContext(Dispatchers.IO) {
|
||||
flow.login(mibCreds.username, mibCreds.passwordHash, mibCreds.otpSeed)
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
app.accounts = mibAccounts
|
||||
app.mibSession = flow.lastSession
|
||||
app.mibProfiles = flow.lastProfiles
|
||||
AccountCache.save(this@HomeActivity, mibAccounts)
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
finally { binding.refreshIndicator.visibility = View.GONE }
|
||||
|
||||
val bmlAccounts = AccountCache.loadBml(this@HomeActivity).toMutableList()
|
||||
if (bmlCreds != null) {
|
||||
try {
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
val (session, accounts) = withContext(Dispatchers.IO) {
|
||||
bmlFlow.login(bmlCreds.username, bmlCreds.password, bmlCreds.otpSeed)
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
AccountCache.saveBml(this@HomeActivity, accounts)
|
||||
bmlAccounts.clear()
|
||||
bmlAccounts.addAll(accounts)
|
||||
refreshBmlContacts(app)
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
lifecycleScope.launch {
|
||||
// MIB and BML login run in parallel
|
||||
val mibJob = mibCreds?.let {
|
||||
async(Dispatchers.IO) {
|
||||
try {
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
val accounts = flow.login(it.username, it.passwordHash, it.otpSeed)
|
||||
val app = application as BasedBankApp
|
||||
app.accounts = accounts
|
||||
app.mibSession = flow.lastSession
|
||||
app.mibProfiles = flow.lastProfiles
|
||||
AccountCache.save(this@HomeActivity, accounts)
|
||||
accounts
|
||||
} catch (_: Exception) { AccountCache.load(this@HomeActivity) }
|
||||
}
|
||||
}
|
||||
|
||||
val bmlJob = bmlCreds?.let {
|
||||
async(Dispatchers.IO) {
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
val savedToken = store.loadBmlSession()
|
||||
|
||||
// Try cached token first
|
||||
if (savedToken != null) {
|
||||
try {
|
||||
val session = BmlSession(savedToken.first, savedToken.second)
|
||||
val accounts = bmlFlow.fetchAccounts(session)
|
||||
val app = application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
AccountCache.saveBml(this@HomeActivity, accounts)
|
||||
return@async Pair(session, accounts)
|
||||
} catch (_: AuthExpiredException) {
|
||||
// Token expired — fall through to full login
|
||||
} catch (_: Exception) {
|
||||
// Network or other error — fall through to full login
|
||||
}
|
||||
}
|
||||
|
||||
// Full login (token missing or expired)
|
||||
try {
|
||||
val (session, accounts) = bmlFlow.login(it.username, it.password, it.otpSeed)
|
||||
store.saveBmlSession(session.accessToken, session.deviceId)
|
||||
val app = application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
AccountCache.saveBml(this@HomeActivity, accounts)
|
||||
Pair(session, accounts)
|
||||
} catch (_: Exception) {
|
||||
Pair(null, AccountCache.loadBml(this@HomeActivity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mibAccounts = mibJob?.await() ?: AccountCache.load(this@HomeActivity)
|
||||
val (bmlSession, bmlAccounts) = bmlJob?.await() ?: Pair(null, AccountCache.loadBml(this@HomeActivity))
|
||||
|
||||
viewModel.accounts.postValue(mibAccounts + bmlAccounts)
|
||||
binding.refreshIndicator.visibility = View.GONE
|
||||
|
||||
val app = application as BasedBankApp
|
||||
if (bmlSession != null) {
|
||||
refreshBmlContacts(app)
|
||||
refreshBmlLimits(bmlSession)
|
||||
}
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
private fun autoRefreshBml(bmlCreds: CredentialStore.BmlCredentials) {
|
||||
binding.refreshIndicator.visibility = View.VISIBLE
|
||||
private fun refreshBmlLimits(session: BmlSession) {
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
lifecycleScope.launch {
|
||||
val cachedMib = AccountCache.load(this@HomeActivity)
|
||||
try {
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
val (session, accounts) = withContext(Dispatchers.IO) {
|
||||
bmlFlow.login(bmlCreds.username, bmlCreds.password, bmlCreds.otpSeed)
|
||||
val (userName, limits) = withContext(Dispatchers.IO) {
|
||||
Pair(bmlFlow.fetchUserInfo(session), bmlFlow.fetchForeignLimits(session))
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
AccountCache.saveBml(this@HomeActivity, accounts)
|
||||
viewModel.accounts.postValue(cachedMib + accounts)
|
||||
refreshBmlContacts(app)
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
finally { binding.refreshIndicator.visibility = View.GONE }
|
||||
val existing = viewModel.bmlLimits.value?.toMutableList() ?: mutableListOf()
|
||||
val idx = existing.indexOfFirst { it.userName == userName }
|
||||
val entry = HomeViewModel.BmlLimitsData(userName, limits)
|
||||
if (idx >= 0) existing[idx] = entry else existing.add(entry)
|
||||
viewModel.bmlLimits.postValue(existing)
|
||||
ForeignLimitsCache.save(this@HomeActivity, existing)
|
||||
} catch (_: Exception) { /* keep previous */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,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.mib.MibAccount
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiary
|
||||
import sh.sar.basedbank.api.mib.MibBeneficiaryCategory
|
||||
@@ -12,4 +13,7 @@ class HomeViewModel : ViewModel() {
|
||||
val financing = MutableLiveData<List<MibFinanceDeal>>(emptyList())
|
||||
val contacts = MutableLiveData<List<MibBeneficiary>>(emptyList())
|
||||
val contactCategories = MutableLiveData<List<MibBeneficiaryCategory>>(emptyList())
|
||||
|
||||
data class BmlLimitsData(val userName: String, val limits: List<BmlForeignLimit>)
|
||||
val bmlLimits = MutableLiveData<List<BmlLimitsData>>(emptyList())
|
||||
}
|
||||
|
||||
@@ -159,7 +159,9 @@ class CredentialsFragment : Fragment() {
|
||||
val (session, accounts) = withContext(Dispatchers.IO) {
|
||||
flow.login(username, password, otpSeed)
|
||||
}
|
||||
CredentialStore(requireContext()).saveBmlCredentials(username, password, otpSeed)
|
||||
val store = CredentialStore(requireContext())
|
||||
store.saveBmlCredentials(username, password, otpSeed)
|
||||
store.saveBmlSession(session.accessToken, session.deviceId)
|
||||
AccountCache.saveBml(requireContext(), accounts)
|
||||
val app = requireActivity().application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
|
||||
Reference in New Issue
Block a user