disable unnessary api requests on saving profile/account disable and also disable all accounts for a profile when that profile is disabled
This commit is contained in:
@@ -244,9 +244,9 @@ class HomeActivity : AppCompatActivity() {
|
||||
val cachedFahipay = AccountCache.loadFahipay(this, store.getFahipayLoginIds())
|
||||
val cachedMfaisa = AccountCache.loadMfaisa(this, store.getMfaisaLoginIds())
|
||||
val merged = cachedMib + cachedBml + cachedFahipay + cachedMfaisa
|
||||
if (merged.isNotEmpty()) viewModel.accounts.value = merged
|
||||
if (merged.isNotEmpty()) viewModel.accounts.value = merged.filterVisibleAccounts()
|
||||
val cachedCards = CardsCache.load(this)
|
||||
if (cachedCards.isNotEmpty()) viewModel.mibCards.value = cachedCards
|
||||
if (cachedCards.isNotEmpty()) viewModel.mibCards.value = cachedCards.filterVisibleCards()
|
||||
val cachedFinancing = FinancingCache.load(this)
|
||||
if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing
|
||||
val cachedBmlLoans = FinancingCache.loadBmlLoans(this)
|
||||
@@ -774,47 +774,13 @@ fun applyNavLabelVisibility() {
|
||||
|
||||
if (savedProfiles.isNotEmpty()) app.bmlProfilesMap[loginId] = savedProfiles
|
||||
|
||||
val bmlClient = BmlAccountClient()
|
||||
// Hidden profiles are never fetched; unhiding one fetches it on its own (fetchEnabledBmlProfiles)
|
||||
val hiddenProfiles = store.getHiddenBmlProfileIds(loginId)
|
||||
for (profile in savedProfiles) {
|
||||
val saved = store.loadBmlProfileSession(profile.profileId)
|
||||
val refreshToken = store.loadBmlProfileRefreshToken(profile.profileId)
|
||||
if (saved == null) {
|
||||
allAccounts += AccountCache.loadBml(this@HomeActivity, loginId)
|
||||
.filter { it.profileId == profile.profileId }
|
||||
continue
|
||||
}
|
||||
val expiresAt = store.loadBmlProfileExpiresAt(profile.profileId)
|
||||
val tokenKnownExpired = expiresAt > 0L && System.currentTimeMillis() >= expiresAt
|
||||
|
||||
suspend fun fetchWithSession(session: BmlSession) {
|
||||
bmlClient.checkProfile(session)
|
||||
val accounts = bmlClient.fetchAccounts(session, loginTag, profile.name, profile.profileId)
|
||||
app.bmlSessions[profile.profileId] = session
|
||||
allAccounts += accounts
|
||||
}
|
||||
|
||||
suspend fun tryRefresh() {
|
||||
if (refreshToken == null) throw Exception("No refresh token")
|
||||
val oldSession = BmlSession(saved.first, saved.second, refreshToken)
|
||||
val newSession = app.bmlFlowFor(loginId).refreshSession(oldSession)
|
||||
store.saveBmlProfileSession(profile.profileId, newSession.accessToken, newSession.deviceId)
|
||||
if (newSession.refreshToken.isNotBlank())
|
||||
store.saveBmlProfileRefreshToken(profile.profileId, newSession.refreshToken)
|
||||
if (newSession.expiresAt > 0)
|
||||
store.saveBmlProfileExpiresAt(profile.profileId, newSession.expiresAt)
|
||||
fetchWithSession(newSession)
|
||||
}
|
||||
|
||||
if (profile.profileId in hiddenProfiles) continue
|
||||
try {
|
||||
if (tokenKnownExpired) {
|
||||
tryRefresh()
|
||||
} else {
|
||||
try {
|
||||
fetchWithSession(BmlSession(saved.first, saved.second))
|
||||
} catch (_: AuthExpiredException) {
|
||||
tryRefresh()
|
||||
}
|
||||
}
|
||||
allAccounts += fetchBmlProfileAccounts(store, loginId, profile)
|
||||
?: AccountCache.loadBml(this@HomeActivity, loginId).filter { it.profileId == profile.profileId }
|
||||
} catch (e: java.io.IOException) {
|
||||
refreshErrors.add("NO_INTERNET")
|
||||
allAccounts += AccountCache.loadBml(this@HomeActivity, loginId)
|
||||
@@ -985,7 +951,7 @@ fun applyNavLabelVisibility() {
|
||||
refreshBmlLoanDetails()
|
||||
for ((loginId, session) in app.mibSessions) {
|
||||
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
|
||||
refreshMibCards(loginId, session, profiles)
|
||||
refreshMibCards(loginId, session, profiles.filterVisibleProfiles(loginId))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1024,10 +990,140 @@ fun applyNavLabelVisibility() {
|
||||
return filter { it.profileId !in hidden }
|
||||
}
|
||||
|
||||
/** Called by SettingsLoginsFragment after the user changes profile visibility. */
|
||||
fun applyProfileVisibility() {
|
||||
val current = viewModel.accounts.value ?: return
|
||||
viewModel.accounts.value = current.filterVisibleAccounts()
|
||||
/** Drops MIB cards belonging to profiles the user has hidden. */
|
||||
private fun List<sh.sar.basedbank.api.mib.MibCard>.filterVisibleCards(): List<sh.sar.basedbank.api.mib.MibCard> {
|
||||
val store = CredentialStore(this@HomeActivity)
|
||||
return filter { card ->
|
||||
card.profileId.isEmpty() || card.profileId !in store.getHiddenMibProfileIds(card.loginTag.removePrefix("mib_"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by SettingsLoginsFragment after the user hides/unhides profiles or accounts.
|
||||
* Re-filters the in-memory data only — no network. Newly unhidden profiles are fetched
|
||||
* separately via [fetchEnabledMibProfiles] / [fetchEnabledBmlProfiles].
|
||||
*/
|
||||
fun applyVisibility() {
|
||||
val app = application as BasedBankApp
|
||||
viewModel.accounts.value = (app.mibAccounts + app.bmlAccounts + app.fahipayAccounts + app.mfaisaAccounts).filterVisibleAccounts()
|
||||
viewModel.mibCards.value?.let { cards ->
|
||||
val visible = cards.filterVisibleCards()
|
||||
viewModel.mibCards.value = visible
|
||||
CardsCache.save(this, visible)
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches accounts, cards and financing for MIB profiles the user just unhid (hidden ones are skipped on refresh). */
|
||||
fun fetchEnabledMibProfiles(loginId: String, profileIds: Set<String>) {
|
||||
if (profileIds.isEmpty()) return
|
||||
val app = application as BasedBankApp
|
||||
val loginTag = "mib_$loginId"
|
||||
val enabled = (app.mibProfilesMap[loginId] ?: emptyList()).filter { it.profileId in profileIds }
|
||||
binding.refreshIndicator.visibility = View.VISIBLE
|
||||
lifecycleScope.launch {
|
||||
val fresh = withContext(Dispatchers.IO) {
|
||||
val session = app.mibSessions[loginId]
|
||||
if (session != null && enabled.isNotEmpty()) {
|
||||
try {
|
||||
val accounts = app.mibFlowFor(loginId).fetchAllProfiles(session, enabled, loginTag)
|
||||
if (accounts.isNotEmpty()) {
|
||||
return@withContext app.mibAccounts.filter { it.loginTag != loginTag || it.profileId !in profileIds } + accounts
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
// Session expired — log this one login in again (it now includes the unhidden profiles)
|
||||
val store = CredentialStore(this@HomeActivity)
|
||||
val creds = store.loadMibCredentials(loginId) ?: return@withContext null
|
||||
try {
|
||||
val flow = MibLoginFlow(store)
|
||||
val accounts = flow.login(creds.username, creds.passwordHash, creds.otpSeed)
|
||||
app.mibSessions[loginId] = flow.lastSession!!
|
||||
app.mibProfilesMap[loginId] = flow.lastProfiles
|
||||
app.mibLoginFlows[loginId] = flow
|
||||
store.saveMibProfiles(loginId, flow.lastProfiles)
|
||||
app.mibAccounts.filter { it.loginTag != loginTag } + accounts
|
||||
} catch (_: Exception) { null }
|
||||
}
|
||||
binding.refreshIndicator.visibility = View.GONE
|
||||
if (fresh == null) return@launch
|
||||
app.mibAccounts = fresh
|
||||
AccountCache.save(this@HomeActivity, fresh)
|
||||
applyVisibility()
|
||||
val session = app.mibSessions[loginId] ?: return@launch
|
||||
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
|
||||
refreshMibCards(loginId, session, profiles.filter { it.profileId in profileIds })
|
||||
// Financing deals carry no profile id, so the login's visible profiles are fetched together
|
||||
refreshFinancing(loginId, session, profiles.filterVisibleProfiles(loginId))
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches accounts (and loan details / limits) for BML profiles the user just unhid. */
|
||||
fun fetchEnabledBmlProfiles(loginId: String, profileIds: Set<String>) {
|
||||
if (profileIds.isEmpty()) return
|
||||
val app = application as BasedBankApp
|
||||
val store = CredentialStore(this)
|
||||
val enabled = store.loadBmlProfiles(loginId).filter { it.profileId in profileIds }
|
||||
if (enabled.isEmpty()) return
|
||||
binding.refreshIndicator.visibility = View.VISIBLE
|
||||
lifecycleScope.launch {
|
||||
val fetched = withContext(Dispatchers.IO) {
|
||||
enabled.mapNotNull { profile ->
|
||||
try { fetchBmlProfileAccounts(store, loginId, profile)?.let { profile.profileId to it } } catch (_: Exception) { null }
|
||||
}
|
||||
}
|
||||
binding.refreshIndicator.visibility = View.GONE
|
||||
if (fetched.isEmpty()) return@launch
|
||||
val fetchedIds = fetched.map { it.first }.toSet()
|
||||
val loginTag = "bml_$loginId"
|
||||
app.bmlAccounts = app.bmlAccounts.filter { it.loginTag != loginTag || it.profileId !in fetchedIds } +
|
||||
fetched.flatMap { it.second }
|
||||
AccountCache.saveBml(this@HomeActivity, loginId, app.bmlAccounts.filter { it.loginTag == loginTag })
|
||||
applyVisibility()
|
||||
fetchedIds.forEach { id -> app.bmlSessions[id]?.let { refreshBmlLimits(it) } }
|
||||
refreshBmlLoanDetails()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches one BML profile's accounts with its saved session, refreshing the token when expired.
|
||||
* Returns null when the profile has no saved session; throws on network/server errors.
|
||||
*/
|
||||
private fun fetchBmlProfileAccounts(store: CredentialStore, loginId: String, profile: BmlProfile): List<BankAccount>? {
|
||||
val app = application as BasedBankApp
|
||||
val saved = store.loadBmlProfileSession(profile.profileId) ?: return null
|
||||
val refreshToken = store.loadBmlProfileRefreshToken(profile.profileId)
|
||||
val expiresAt = store.loadBmlProfileExpiresAt(profile.profileId)
|
||||
val tokenKnownExpired = expiresAt > 0L && System.currentTimeMillis() >= expiresAt
|
||||
val bmlClient = BmlAccountClient()
|
||||
|
||||
fun fetchWithSession(session: BmlSession): List<BankAccount> {
|
||||
bmlClient.checkProfile(session)
|
||||
val accounts = bmlClient.fetchAccounts(session, "bml_$loginId", profile.name, profile.profileId)
|
||||
app.bmlSessions[profile.profileId] = session
|
||||
return accounts
|
||||
}
|
||||
|
||||
fun tryRefresh(): List<BankAccount> {
|
||||
if (refreshToken == null) throw Exception("No refresh token")
|
||||
val oldSession = BmlSession(saved.first, saved.second, refreshToken)
|
||||
val newSession = app.bmlFlowFor(loginId).refreshSession(oldSession)
|
||||
store.saveBmlProfileSession(profile.profileId, newSession.accessToken, newSession.deviceId)
|
||||
if (newSession.refreshToken.isNotBlank())
|
||||
store.saveBmlProfileRefreshToken(profile.profileId, newSession.refreshToken)
|
||||
if (newSession.expiresAt > 0)
|
||||
store.saveBmlProfileExpiresAt(profile.profileId, newSession.expiresAt)
|
||||
return fetchWithSession(newSession)
|
||||
}
|
||||
|
||||
return if (tokenKnownExpired) {
|
||||
tryRefresh()
|
||||
} else {
|
||||
try {
|
||||
fetchWithSession(BmlSession(saved.first, saved.second))
|
||||
} catch (_: AuthExpiredException) {
|
||||
tryRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshBmlLimits(session: BmlSession) {
|
||||
@@ -1261,7 +1357,7 @@ fun applyNavLabelVisibility() {
|
||||
val app = application as BasedBankApp
|
||||
for ((loginId, session) in app.mibSessions) {
|
||||
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
|
||||
refreshMibCards(loginId, session, profiles)
|
||||
refreshMibCards(loginId, session, profiles.filterVisibleProfiles(loginId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1286,7 +1382,8 @@ fun applyNavLabelVisibility() {
|
||||
}
|
||||
if (cards.isNotEmpty()) {
|
||||
val existing = viewModel.mibCards.value?.toMutableList() ?: mutableListOf()
|
||||
existing.removeAll { it.loginTag == "mib_$loginId" }
|
||||
val fetchedIds = profiles.map { it.profileId }.toSet()
|
||||
existing.removeAll { it.loginTag == "mib_$loginId" && (it.profileId in fetchedIds || it.profileId.isEmpty()) }
|
||||
existing += cards
|
||||
viewModel.mibCards.postValue(existing)
|
||||
CardsCache.save(this@HomeActivity, existing)
|
||||
|
||||
@@ -573,6 +573,27 @@ class SettingsLoginsFragment : Fragment() {
|
||||
return accounts.map { addAccountRow(ctx, container, dp, it, hiddenAccounts, indent = false) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Unchecks and disables account toggles whose parent profile is hidden, and restores the
|
||||
* account's own choice once the profile is shown again. [hiddenAccounts] is left untouched —
|
||||
* the account listeners ignore changes made while a toggle is disabled.
|
||||
*/
|
||||
private fun syncAccountToggles(
|
||||
accountRows: List<Pair<BankAccount, MaterialSwitch>>,
|
||||
hiddenAccounts: Set<String>,
|
||||
isParentHidden: (BankAccount) -> Boolean
|
||||
) {
|
||||
accountRows.forEach { (acc, toggle) ->
|
||||
if (isParentHidden(acc)) {
|
||||
toggle.isEnabled = false
|
||||
toggle.isChecked = false
|
||||
} else {
|
||||
toggle.isChecked = acc.accountNumber !in hiddenAccounts
|
||||
toggle.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges this login's account-hide choices into the global hidden-accounts set, leaving other logins untouched. */
|
||||
private fun persistHiddenAccounts(store: CredentialStore, scopedAccounts: List<BankAccount>, hiddenAccounts: Set<String>) {
|
||||
val scopedNumbers = scopedAccounts.map { it.accountNumber }.toSet()
|
||||
@@ -696,6 +717,7 @@ class SettingsLoginsFragment : Fragment() {
|
||||
// Disable the sole remaining visible toggle so it can't be turned off
|
||||
toggle.isEnabled = !(toggle.isChecked && visibleCount == 1)
|
||||
}
|
||||
syncAccountToggles(accountRows, hiddenAccounts) { it.profileId in hidden }
|
||||
saveBtn.isEnabled = (hidden != originalHidden || hiddenAccounts != originalHiddenAccounts) && visibleCount >= 1
|
||||
}
|
||||
|
||||
@@ -722,6 +744,7 @@ class SettingsLoginsFragment : Fragment() {
|
||||
|
||||
accountRows.forEach { (acc, toggle) ->
|
||||
toggle.setOnCheckedChangeListener { _, checked ->
|
||||
if (!toggle.isEnabled) return@setOnCheckedChangeListener // driven by a hidden parent profile
|
||||
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
||||
updateToggleStates(saveBtn)
|
||||
}
|
||||
@@ -730,9 +753,11 @@ class SettingsLoginsFragment : Fragment() {
|
||||
saveBtn.setOnClickListener {
|
||||
store.setHiddenMibProfileIds(loginId, hidden)
|
||||
persistHiddenAccounts(store, loginAccounts, hiddenAccounts)
|
||||
clearAllCaches(ctx)
|
||||
dialog.dismiss()
|
||||
(activity as? HomeActivity)?.relogin()
|
||||
// Hiding is applied offline; only profiles that were just unhidden get fetched
|
||||
val home = activity as? HomeActivity
|
||||
home?.applyVisibility()
|
||||
home?.fetchEnabledMibProfiles(loginId, originalHidden - hidden)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +894,7 @@ class SettingsLoginsFragment : Fragment() {
|
||||
toggleRows.forEach { (_, toggle) ->
|
||||
toggle.isEnabled = !(toggle.isChecked && visibleCount == 1)
|
||||
}
|
||||
syncAccountToggles(accountRows, hiddenAccounts) { it.profileId in hidden }
|
||||
saveBtn.isEnabled = (hidden != originalHidden || hiddenAccounts != originalHiddenAccounts) && visibleCount >= 1
|
||||
}
|
||||
|
||||
@@ -906,6 +932,7 @@ class SettingsLoginsFragment : Fragment() {
|
||||
|
||||
accountRows.forEach { (acc, toggle) ->
|
||||
toggle.setOnCheckedChangeListener { _, checked ->
|
||||
if (!toggle.isEnabled) return@setOnCheckedChangeListener // driven by a hidden parent profile
|
||||
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
||||
updateToggleStates(saveBtn)
|
||||
}
|
||||
@@ -914,9 +941,11 @@ class SettingsLoginsFragment : Fragment() {
|
||||
saveBtn.setOnClickListener {
|
||||
store.setHiddenBmlProfileIds(loginId, hidden)
|
||||
persistHiddenAccounts(store, loginAccounts, hiddenAccounts)
|
||||
clearAllCaches(ctx)
|
||||
dialog.dismiss()
|
||||
(activity as? HomeActivity)?.relogin()
|
||||
// Hiding is applied offline; only profiles that were just unhidden get fetched
|
||||
val home = activity as? HomeActivity
|
||||
home?.applyVisibility()
|
||||
home?.fetchEnabledBmlProfiles(loginId, originalHidden - hidden)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,9 +1249,8 @@ class SettingsLoginsFragment : Fragment() {
|
||||
|
||||
saveBtn.setOnClickListener {
|
||||
persistHiddenAccounts(store, loginAccounts, hiddenAccounts)
|
||||
clearAllCaches(ctx)
|
||||
dialog.dismiss()
|
||||
(activity as? HomeActivity)?.relogin()
|
||||
(activity as? HomeActivity)?.applyVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1398,6 +1426,7 @@ class SettingsLoginsFragment : Fragment() {
|
||||
toggleRows.forEach { (_, toggle) ->
|
||||
toggle.isEnabled = !(toggle.isChecked && visibleCount == 1)
|
||||
}
|
||||
syncAccountToggles(accountRows, hiddenAccounts) { it.accountNumber in hidden }
|
||||
saveBtn.isEnabled = (hidden != originalHidden || hiddenAccounts != originalHiddenAccounts) && visibleCount >= 1
|
||||
}
|
||||
|
||||
@@ -1427,17 +1456,18 @@ class SettingsLoginsFragment : Fragment() {
|
||||
|
||||
accountRows.forEach { (acc, toggle) ->
|
||||
toggle.setOnCheckedChangeListener { _, checked ->
|
||||
if (!toggle.isEnabled) return@setOnCheckedChangeListener // driven by a hidden pocket group
|
||||
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
||||
updateToggleStates(saveBtn)
|
||||
}
|
||||
}
|
||||
|
||||
saveBtn.setOnClickListener {
|
||||
// All pockets come back in one login response, so hiding/unhiding never needs a request
|
||||
store.setHiddenMfaisaPocketIds(loginId, hidden)
|
||||
persistHiddenAccounts(store, pockets, hiddenAccounts)
|
||||
clearAllCaches(ctx)
|
||||
dialog.dismiss()
|
||||
(activity as? HomeActivity)?.relogin()
|
||||
(activity as? HomeActivity)?.applyVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user