better network error handling, fix crash when no network in transaction history page
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 6s

This commit is contained in:
2026-05-28 00:14:11 +05:00
parent 0560c53ae3
commit c4d3c1efd4
7 changed files with 78 additions and 33 deletions

View File

@@ -150,8 +150,15 @@ class AccountHistoryFragment : Fragment() {
pendingIconUrls.clear()
firstPageDone = false
fetcher = HistoryFetcher(account)
adapter.setTransactions(emptyList())
binding.emptyView.visibility = View.GONE
// Restore cache immediately so data stays visible while refreshing
val cached = TransactionCache.load(requireContext(), account.accountNumber)
if (cached.isNotEmpty()) {
allTransactions.addAll(cached)
filterAndDisplay()
} else {
adapter.setTransactions(emptyList())
binding.emptyView.visibility = View.GONE
}
loadNextPage()
}
@@ -179,6 +186,7 @@ class AccountHistoryFragment : Fragment() {
}
isLoading = false
if (_binding == null) return@launch
if (!firstPageDone) {
firstPageDone = true
@@ -191,6 +199,7 @@ class AccountHistoryFragment : Fragment() {
if (allTransactions.isEmpty()) binding.emptyView.visibility = View.VISIBLE
return@launch
}
(activity as? HomeActivity)?.hideConnectivityBanner()
if (transactions.isNotEmpty()) {
val existingIds = allTransactions.map { it.id }.toHashSet()

View File

@@ -147,7 +147,7 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() {
viewModel.accounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() }
viewModel.hideAmounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() }
(activity as? HomeActivity)?.loadAllContacts()
(activity as? HomeActivity)?.triggerRefresh()
}
private fun attachMediator(pages: List<TabDef>) {

View File

@@ -137,7 +137,7 @@ class ContactsFragment : Fragment() {
binding.swipeRefresh.setOnRefreshListener {
contactsRefreshing = true
(activity as? HomeActivity)?.loadAllContacts()
(activity as? HomeActivity)?.triggerRefresh()
}
viewModel.contactCategories.observe(viewLifecycleOwner) { cats ->

View File

@@ -47,7 +47,7 @@ class FinancingFragment : Fragment() {
binding.swipeRefresh.setOnRefreshListener {
financingRefreshing = true
(activity as? HomeActivity)?.triggerRefreshFinancing()
(activity as? HomeActivity)?.triggerRefresh()
}
viewModel.accounts.observe(viewLifecycleOwner) { rebuildAdapter() }

View File

@@ -554,7 +554,7 @@ fun applyNavLabelVisibility() {
binding.connectivityBanner.visibility = View.VISIBLE
}
private fun hideConnectivityBanner() {
fun hideConnectivityBanner() {
binding.connectivityBanner.visibility = View.GONE
}

View File

@@ -859,7 +859,11 @@ class TransferFragment : Fragment() {
activity.triggerRefresh()
activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar))
} else if (!ok) {
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
if (msg == "CONNECTIVITY") {
(activity as? HomeActivity)?.showConnectivityBanner(getString(R.string.connectivity_no_internet))
} else {
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
}
}
}
}
@@ -1069,7 +1073,7 @@ class TransferFragment : Fragment() {
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null)
}
} catch (e: Exception) {
Triple(false, e.message ?: "Transfer failed", null)
Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Transfer failed"), null)
}
}
@@ -1122,7 +1126,7 @@ class TransferFragment : Fragment() {
// Step 1: initiate
val initiated = try {
BmlTransferClient().initiateTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, bank)
} catch (e: Exception) { return Triple(false, e.message ?: "Initiation failed", null) }
} catch (e: Exception) { return Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Initiation failed"), null) }
if (!initiated) return Triple(false, "Failed to initiate transfer — check your session", null)
@@ -1154,7 +1158,7 @@ class TransferFragment : Fragment() {
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null)
}
} catch (e: Exception) {
Triple(false, e.message ?: "Transfer failed", null)
Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Transfer failed"), null)
}
}
@@ -1396,7 +1400,7 @@ class TransferFragment : Fragment() {
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null as TransferReceiptData?)
}
} catch (e: Exception) {
Triple(false, e.message ?: "Transfer failed", null as TransferReceiptData?)
Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Transfer failed"), null as TransferReceiptData?)
}
}
(activity as? HomeActivity)?.setRefreshing(false)
@@ -1410,7 +1414,11 @@ class TransferFragment : Fragment() {
activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar))
} else {
binding.btnTransfer.isEnabled = true
binding.tilBmlOtp.error = msg
if (msg == "CONNECTIVITY") {
(activity as? HomeActivity)?.showConnectivityBanner(getString(R.string.connectivity_no_internet))
} else {
binding.tilBmlOtp.error = msg
}
}
}
}

View File

@@ -30,6 +30,7 @@ import sh.sar.basedbank.api.fahipay.FahipayHistoryClient
import sh.sar.basedbank.api.models.BankAccount
import sh.sar.basedbank.api.mib.MibContactsClient
import sh.sar.basedbank.api.mib.MibHistoryClient
import sh.sar.basedbank.api.models.BankServerException
import sh.sar.basedbank.api.models.BankTransaction
import sh.sar.basedbank.api.mib.TransactionCache
import sh.sar.basedbank.databinding.FragmentTransferHistoryBinding
@@ -161,8 +162,15 @@ class TransferHistoryFragment : Fragment() {
val accounts = accountStates.map { it.account }
accountStates.clear()
accounts.forEach { accountStates.add(AccountState(it)) }
adapter.setTransactions(emptyList())
binding.emptyView.visibility = View.GONE
// Restore cache immediately so data stays visible while refreshing
val cached = TransactionCache.load(requireContext(), "transfer")
if (cached.isNotEmpty()) {
allTransactions.addAll(cached)
filterAndDisplay()
} else {
adapter.setTransactions(emptyList())
binding.emptyView.visibility = View.GONE
}
loadNextPages()
}
@@ -178,6 +186,14 @@ class TransferHistoryFragment : Fragment() {
val app = requireActivity().application as BasedBankApp
lifecycleScope.launch {
val bannerMsg = java.util.concurrent.atomic.AtomicReference<String?>(null)
fun trackError(e: Exception) {
when {
e is java.io.IOException -> bannerMsg.compareAndSet(null, "IO")
e is BankServerException -> bannerMsg.compareAndSet(null, "SERVER:${e.bankName}")
}
}
val newTransactions = withContext(Dispatchers.IO) {
val results = mutableListOf<BankTransaction>()
@@ -215,7 +231,7 @@ class TransferHistoryFragment : Fragment() {
list
}
}
} catch (_: Exception) { emptyList<BankTransaction>() }
} catch (e: Exception) { trackError(e); emptyList<BankTransaction>() }
}
}.awaitAll().flatten())
@@ -233,7 +249,7 @@ class TransferHistoryFragment : Fragment() {
if (total > 0) state.fahipayTotal = total
state.fahipayNextStart += list.size
results.addAll(list)
} catch (_: Exception) {}
} catch (e: Exception) { trackError(e) }
}
// MIB accounts: serialized per profile, protected by mutex to prevent session race
@@ -242,23 +258,25 @@ class TransferHistoryFragment : Fragment() {
val session = app.mibSessions[loginId] ?: continue
for ((profileId, states) in loginStates.groupBy { it.account.profileId }) {
app.mibMutex.withLock {
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
val profile = profiles.firstOrNull { it.profileId == profileId }
if (profile != null) app.mibFlowFor(loginId).switchProfile(session, profile)
for (state in states) {
try {
val (list, total) = MibHistoryClient().fetchHistory(
session = session,
accountNo = state.account.accountNumber,
accountDisplayName = state.account.accountBriefName,
start = state.mibNextStart,
pageSize = pageSize
)
if (total > 0) state.mibTotalCount = total
state.mibNextStart += list.size.coerceAtLeast(pageSize)
results.addAll(list)
} catch (_: Exception) {}
}
try {
val profiles = app.mibProfilesMap[loginId] ?: emptyList()
val profile = profiles.firstOrNull { it.profileId == profileId }
if (profile != null) app.mibFlowFor(loginId).switchProfile(session, profile)
for (state in states) {
try {
val (list, total) = MibHistoryClient().fetchHistory(
session = session,
accountNo = state.account.accountNumber,
accountDisplayName = state.account.accountBriefName,
start = state.mibNextStart,
pageSize = pageSize
)
if (total > 0) state.mibTotalCount = total
state.mibNextStart += list.size.coerceAtLeast(pageSize)
results.addAll(list)
} catch (e: Exception) { trackError(e) }
}
} catch (e: Exception) { trackError(e) }
}
}
}
@@ -267,6 +285,16 @@ class TransferHistoryFragment : Fragment() {
}
isLoading = false
if (_binding == null) return@launch
val raw = bannerMsg.get()
when {
raw == null -> (activity as? HomeActivity)?.hideConnectivityBanner()
raw == "IO" -> (activity as? HomeActivity)?.showConnectivityBanner(getString(R.string.connectivity_no_internet))
raw.startsWith("SERVER:") -> (activity as? HomeActivity)?.showConnectivityBanner(
getString(R.string.connectivity_server_error, raw.removePrefix("SERVER:"))
)
}
if (!firstBatchDone) {
firstBatchDone = true